最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

JavaScript判斷dom元素是否在可視區(qū)域的常規(guī)方式總結(jié)

 更新時(shí)間:2025年12月02日 08:21:51   作者:拉不動(dòng)的豬  
這篇文章主要為大家詳細(xì)介紹了JavaScript判斷dom元素是否在可視區(qū)域的常規(guī)方式,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

1. Intersection Observer API(推薦)

這是現(xiàn)代瀏覽器推薦的方法,性能最好,異步執(zhí)行,不會(huì)阻塞主線程。

基礎(chǔ)用法

// 創(chuàng)建觀察器
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      console.log('元素進(jìn)入可視區(qū)域', entry.target);
      // 可以在這里執(zhí)行懶加載等操作
      entry.target.classList.add('visible');
    } else {
      console.log('元素離開(kāi)可視區(qū)域', entry.target);
      entry.target.classList.remove('visible');
    }
  });
});

// 觀察元素
const elements = document.querySelectorAll('.watch-element');
elements.forEach(el => observer.observe(el));

高級(jí)配置

const options = {
  // root: 指定根元素,默認(rèn)為瀏覽器視窗
  root: null, // 或者指定特定元素,如 document.querySelector('.container')
  
  // rootMargin: 根的外邊距,可以擴(kuò)大或縮小根的邊界框
  rootMargin: '10px 0px -100px 0px', // 上右下左,類(lèi)似CSS margin
  
  // threshold: 觸發(fā)回調(diào)的可見(jiàn)比例
  threshold: [0, 0.25, 0.5, 0.75, 1] // 在0%, 25%, 50%, 75%, 100%可見(jiàn)時(shí)觸發(fā)
};

const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    const visiblePercentage = Math.round(entry.intersectionRatio * 100);
    console.log(`元素可見(jiàn) ${visiblePercentage}%`);
    
    // 根據(jù)可見(jiàn)比例執(zhí)行不同操作
    if (entry.intersectionRatio > 0.5) {
      // 超過(guò)50%可見(jiàn)
      entry.target.classList.add('mostly-visible');
    }
  });
}, options);

實(shí)用工具函數(shù)

// 封裝的工具函數(shù)
function createVisibilityObserver(options = {}) {
  const defaultOptions = {
    root: null,
    rootMargin: '0px',
    threshold: 0.1
  };
  
  const finalOptions = { ...defaultOptions, ...options };
  
  return new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      const element = entry.target;
      const isVisible = entry.isIntersecting;
      
      // 觸發(fā)自定義事件
      element.dispatchEvent(new CustomEvent('visibilityChange', {
        detail: { isVisible, entry }
      }));
    });
  }, finalOptions);
}

// 使用示例
const observer = createVisibilityObserver({ threshold: 0.5 });

document.querySelectorAll('.lazy-load').forEach(element => {
  observer.observe(element);
  
  element.addEventListener('visibilityChange', (e) => {
    if (e.detail.isVisible) {
      // 執(zhí)行懶加載
      const img = element.querySelector('img[data-src]');
      if (img) {
        img.src = img.dataset.src;
        img.removeAttribute('data-src');
      }
    }
  });
});

2. getBoundingClientRect() 方法

傳統(tǒng)方法,同步執(zhí)行,需要手動(dòng)調(diào)用。

基礎(chǔ)用法

function isInViewport(element) {
  const rect = element.getBoundingClientRect();
  const windowHeight = window.innerHeight || document.documentElement.clientHeight;
  const windowWidth = window.innerWidth || document.documentElement.clientWidth;
  
  return (
    rect.top >= 0 &&
    rect.left >= 0 &&
    rect.bottom <= windowHeight &&
    rect.right <= windowWidth
  );
}

// 使用示例
const element = document.querySelector('.target');
if (isInViewport(element)) {
  console.log('元素完全在視窗內(nèi)');
}

部分可見(jiàn)判斷

function isPartiallyInViewport(element) {
  const rect = element.getBoundingClientRect();
  const windowHeight = window.innerHeight || document.documentElement.clientHeight;
  const windowWidth = window.innerWidth || document.documentElement.clientWidth;
  
  return (
    rect.bottom > 0 &&
    rect.top < windowHeight &&
    rect.right > 0 &&
    rect.left < windowWidth
  );
}

// 更詳細(xì)的可見(jiàn)性信息
function getVisibilityInfo(element) {
  const rect = element.getBoundingClientRect();
  const windowHeight = window.innerHeight;
  const windowWidth = window.innerWidth;
  
  // 計(jì)算可見(jiàn)區(qū)域
  const visibleTop = Math.max(0, rect.top);
  const visibleLeft = Math.max(0, rect.left);
  const visibleBottom = Math.min(windowHeight, rect.bottom);
  const visibleRight = Math.min(windowWidth, rect.right);
  
  const visibleWidth = Math.max(0, visibleRight - visibleLeft);
  const visibleHeight = Math.max(0, visibleBottom - visibleTop);
  const visibleArea = visibleWidth * visibleHeight;
  const totalArea = rect.width * rect.height;
  
  return {
    isVisible: visibleArea > 0,
    isFullyVisible: isInViewport(element),
    visibilityRatio: totalArea > 0 ? visibleArea / totalArea : 0,
    rect: rect,
    visibleArea: { width: visibleWidth, height: visibleHeight }
  };
}

// 使用示例
const element = document.querySelector('.target');
const info = getVisibilityInfo(element);
console.log(`可見(jiàn)比例: ${(info.visibilityRatio * 100).toFixed(2)}%`);

滾動(dòng)監(jiān)聽(tīng)版本

class ScrollVisibilityTracker {
  constructor(options = {}) {
    this.elements = new Map();
    this.threshold = options.threshold || 0.1;
    this.throttleDelay = options.throttleDelay || 100;
    
    this.checkVisibility = this.throttle(this.checkVisibility.bind(this), this.throttleDelay);
    this.bindEvents();
  }
  
  observe(element, callback) {
    this.elements.set(element, {
      callback,
      wasVisible: false
    });
    
    // 初始檢查
    this.checkElement(element);
  }
  
  unobserve(element) {
    this.elements.delete(element);
  }
  
  checkVisibility() {
    this.elements.forEach((data, element) => {
      this.checkElement(element);
    });
  }
  
  checkElement(element) {
    const data = this.elements.get(element);
    if (!data) return;
    
    const info = getVisibilityInfo(element);
    const isVisible = info.visibilityRatio >= this.threshold;
    
    if (isVisible !== data.wasVisible) {
      data.wasVisible = isVisible;
      data.callback(isVisible, info);
    }
  }
  
  bindEvents() {
    window.addEventListener('scroll', this.checkVisibility, { passive: true });
    window.addEventListener('resize', this.checkVisibility);
  }
  
  destroy() {
    window.removeEventListener('scroll', this.checkVisibility);
    window.removeEventListener('resize', this.checkVisibility);
    this.elements.clear();
  }
  
  throttle(func, delay) {
    let timeoutId;
    let lastExecTime = 0;
    
    return function (...args) {
      const currentTime = Date.now();
      
      if (currentTime - lastExecTime > delay) {
        func.apply(this, args);
        lastExecTime = currentTime;
      } else {
        clearTimeout(timeoutId);
        timeoutId = setTimeout(() => {
          func.apply(this, args);
          lastExecTime = Date.now();
        }, delay - (currentTime - lastExecTime));
      }
    };
  }
}

// 使用示例
const tracker = new ScrollVisibilityTracker({ threshold: 0.5 });

document.querySelectorAll('.track-element').forEach(element => {
  tracker.observe(element, (isVisible, info) => {
    if (isVisible) {
      element.classList.add('in-view');
      console.log('元素進(jìn)入視窗', info);
    } else {
      element.classList.remove('in-view');
    }
  });
});

3. 特殊場(chǎng)景的解決方案

在滾動(dòng)容器中的元素

function isInScrollContainer(element, container) {
  const elementRect = element.getBoundingClientRect();
  const containerRect = container.getBoundingClientRect();
  
  return (
    elementRect.top >= containerRect.top &&
    elementRect.left >= containerRect.left &&
    elementRect.bottom <= containerRect.bottom &&
    elementRect.right <= containerRect.right
  );
}

// 使用Intersection Observer觀察滾動(dòng)容器
function createContainerObserver(container) {
  return new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      console.log('容器內(nèi)元素可見(jiàn)性變化', entry.isIntersecting);
    });
  }, {
    root: container, // 指定容器為根元素
    threshold: 0.1
  });
}

考慮CSS Transform的情況

function getTransformedBounds(element) {
  const rect = element.getBoundingClientRect();
  
  // 如果元素有CSS transform,getBoundingClientRect已經(jīng)包含了變換后的位置
  // 不需要額外計(jì)算
  return rect;
}

// 對(duì)于復(fù)雜的3D變換,可能需要更精確的計(jì)算
function isTransformedElementVisible(element) {
  const rect = element.getBoundingClientRect();
  
  // 檢查元素是否因?yàn)閠ransform: scale(0)等而不可見(jiàn)
  const computedStyle = getComputedStyle(element);
  const transform = computedStyle.transform;
  
  if (transform === 'none') {
    return isPartiallyInViewport(element);
  }
  
  // 檢查是否有scale(0)或類(lèi)似的變換
  if (rect.width === 0 || rect.height === 0) {
    return false;
  }
  
  return isPartiallyInViewport(element);
}

4. 性能優(yōu)化技巧

虛擬滾動(dòng)場(chǎng)景

class VirtualScrollObserver {
  constructor(container, options = {}) {
    this.container = container;
    this.itemHeight = options.itemHeight || 100;
    this.buffer = options.buffer || 5; // 緩沖區(qū)項(xiàng)目數(shù)量
    this.items = [];
    this.visibleRange = { start: 0, end: 0 };
    
    this.handleScroll = this.throttle(this.calculateVisibleRange.bind(this), 16);
    this.container.addEventListener('scroll', this.handleScroll);
  }
  
  calculateVisibleRange() {
    const scrollTop = this.container.scrollTop;
    const containerHeight = this.container.clientHeight;
    
    const start = Math.max(0, Math.floor(scrollTop / this.itemHeight) - this.buffer);
    const end = Math.min(
      this.items.length - 1,
      Math.ceil((scrollTop + containerHeight) / this.itemHeight) + this.buffer
    );
    
    if (start !== this.visibleRange.start || end !== this.visibleRange.end) {
      this.visibleRange = { start, end };
      this.onVisibleRangeChange(this.visibleRange);
    }
  }
  
  onVisibleRangeChange(range) {
    // 子類(lèi)實(shí)現(xiàn)或通過(guò)回調(diào)處理
    console.log('可見(jiàn)范圍變化:', range);
  }
  
  throttle(func, delay) {
    let lastTime = 0;
    return function(...args) {
      const now = Date.now();
      if (now - lastTime >= delay) {
        func.apply(this, args);
        lastTime = now;
      }
    };
  }
}

懶加載圖片完整實(shí)現(xiàn)

class LazyImageLoader {
  constructor(options = {}) {
    this.options = {
      rootMargin: '50px',
      threshold: 0.1,
      loadingClass: 'lazy-loading',
      loadedClass: 'lazy-loaded',
      errorClass: 'lazy-error',
      ...options
    };
    
    this.observer = new IntersectionObserver(
      this.handleIntersection.bind(this),
      {
        rootMargin: this.options.rootMargin,
        threshold: this.options.threshold
      }
    );
    
    this.loadingImages = new Set();
  }
  
  observe(img) {
    if (!(img instanceof HTMLImageElement)) {
      console.warn('LazyImageLoader: 只能觀察img元素');
      return;
    }
    
    if (!img.dataset.src && !img.dataset.srcset) {
      console.warn('LazyImageLoader: 圖片缺少data-src或data-srcset屬性');
      return;
    }
    
    this.observer.observe(img);
  }
  
  handleIntersection(entries) {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        this.loadImage(entry.target);
        this.observer.unobserve(entry.target);
      }
    });
  }
  
  loadImage(img) {
    if (this.loadingImages.has(img)) return;
    
    this.loadingImages.add(img);
    img.classList.add(this.options.loadingClass);
    
    const tempImg = new Image();
    
    tempImg.onload = () => {
      this.applyImage(img, tempImg);
      img.classList.remove(this.options.loadingClass);
      img.classList.add(this.options.loadedClass);
      this.loadingImages.delete(img);
    };
    
    tempImg.onerror = () => {
      img.classList.remove(this.options.loadingClass);
      img.classList.add(this.options.errorClass);
      this.loadingImages.delete(img);
    };
    
    // 支持srcset
    if (img.dataset.srcset) {
      tempImg.srcset = img.dataset.srcset;
    }
    tempImg.src = img.dataset.src;
  }
  
  applyImage(img, tempImg) {
    if (img.dataset.srcset) {
      img.srcset = img.dataset.srcset;
      delete img.dataset.srcset;
    }
    img.src = tempImg.src;
    delete img.dataset.src;
  }
  
  destroy() {
    this.observer.disconnect();
    this.loadingImages.clear();
  }
}

// 使用示例
const lazyLoader = new LazyImageLoader({
  rootMargin: '100px',
  threshold: 0.1
});

document.querySelectorAll('img[data-src]').forEach(img => {
  lazyLoader.observe(img);
});

5. 兼容性處理

// Intersection Observer polyfill檢查
function createCompatibleObserver(callback, options) {
  if ('IntersectionObserver' in window) {
    return new IntersectionObserver(callback, options);
  } else {
    console.warn('IntersectionObserver not supported, falling back to scroll listener');
    return createScrollBasedObserver(callback, options);
  }
}

function createScrollBasedObserver(callback, options = {}) {
  const elements = new Set();
  const threshold = options.threshold || 0;
  
  function checkElements() {
    const entries = [];
    
    elements.forEach(element => {
      const info = getVisibilityInfo(element);
      const isIntersecting = info.visibilityRatio >= threshold;
      
      entries.push({
        target: element,
        isIntersecting,
        intersectionRatio: info.visibilityRatio,
        boundingClientRect: info.rect
      });
    });
    
    if (entries.length > 0) {
      callback(entries);
    }
  }
  
  const throttledCheck = throttle(checkElements, 100);
  
  window.addEventListener('scroll', throttledCheck, { passive: true });
  window.addEventListener('resize', throttledCheck);
  
  return {
    observe(element) {
      elements.add(element);
      // 立即檢查一次
      setTimeout(() => {
        const info = getVisibilityInfo(element);
        const isIntersecting = info.visibilityRatio >= threshold;
        callback([{
          target: element,
          isIntersecting,
          intersectionRatio: info.visibilityRatio,
          boundingClientRect: info.rect
        }]);
      }, 0);
    },
    
    unobserve(element) {
      elements.delete(element);
    },
    
    disconnect() {
      window.removeEventListener('scroll', throttledCheck);
      window.removeEventListener('resize', throttledCheck);
      elements.clear();
    }
  };
}

總結(jié)

選擇合適的方法:

  • Intersection Observer API - 現(xiàn)代瀏覽器首選,性能最佳
  • getBoundingClientRect + 滾動(dòng)監(jiān)聽(tīng) - 需要兼容老瀏覽器時(shí)使用
  • 虛擬滾動(dòng) - 處理大量元素時(shí)的特殊優(yōu)化

關(guān)鍵考慮因素:

  • 性能:Intersection Observer > 節(jié)流的滾動(dòng)監(jiān)聽(tīng) > 頻繁的滾動(dòng)監(jiān)聽(tīng)
  • 精確度:getBoundingClientRect更精確,但需要手動(dòng)觸發(fā)
  • 兼容性:getBoundingClientRect支持更老的瀏覽器
  • 功能需求:是否需要部分可見(jiàn)、可見(jiàn)比例等詳細(xì)信息

到此這篇關(guān)于JavaScript判斷dom元素是否在可視區(qū)域的常規(guī)方式總結(jié)的文章就介紹到這了,更多相關(guān)JavaScript判斷dom元素內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

长汀县| 汤阴县| 水富县| 团风县| 钦州市| 太谷县| 呼玛县| 长顺县| 大厂| 临城县| 巢湖市| 河源市| 汕头市| 盐亭县| 赣州市| 仙桃市| 东港市| 余江县| 镇原县| 淮南市| 宝坻区| 湘西| 桑植县| 乌兰县| 红桥区| 博乐市| 莎车县| 中牟县| 鄂州市| 灵台县| 万山特区| 洪江市| 马龙县| 邳州市| 合川市| 汕尾市| 通道| 婺源县| 南川市| 建瓯市| 张家口市|