js 图片懒加载

网页开发时,常常需要了解某个元素是否进入了"视口"(viewport),即用户能不能看到它。最常见的需求就是图片的懒加载。

有时,我们希望某些静态资源(比如图片),只有用户向下滚动,它们进入视口时才加载,这样可以节省带宽,提高网页性能。这就叫做"惰性加载"。

传统方式(scroll)

传统的实现方法是,监听到 scroll 事件后,调用目标元素的 getBoundingClientRect() 方法,得到它对应于视口左上角的坐标,再判断是否在视口之内。这种方法的缺点是,由于 scroll 事件密集发生,计算量很大,容易造成性能问题。

let t;
// 监听滑动事件
$el.addEventListener('scroll', () => {
  clearTimeout(t);
  t = setTimeout(() => {
    let height = $el.getBoundingClientRect().height; // 滚动所在容器的可视高度
    let top = $el.scrollTop; // 滚动距离
    let end = height + top;
    let $lazyimgElem = $el.querySelector('img'); // 懒加载的图片节点
    let elemTop = $lazyimgElem.offsetTop - top;
    // 始终只加载在当前屏范围内的图片
    if (elemTop >= top && elemTop <= end) {
      $item.src = $item.getAttribute('lazy-src');
      $item.removeAttribute('lazy-src');
      if (elemTop <= end) {
        let imageSrc = $item.getAttribute('lazy-src');
        // 判断图片是否已经加载
        if (imageSrc != null) {
          $item.src = $item.getAttribute('lazy-src');
          $item.removeAttribute('lazy-src');
        }
      }
    }
  }, 100)
});

在这里面由于 scroll 事件是每次滚动都能触发所以,在我们的滑动事件里面有可能会频繁获取节点,判断节点是已经加载图片,这里只是获取一个图片,如果是一个列表,我们要获取更多的节点话,就会使的计算量很大,容易造成性能问题。使用下面的方法就能避免这个问题。

IntersectionObserver

目前有一个新的 IntersectionObserver API,可以自动"观察"元素是否可见。使用这个对象可以很方便简单的实现图片的懒加载。

// 获取所有的懒加载的图片元素
const $images = document.querySelectorAll('.image');

// 构建观察器
let observer = new IntersectionObserver((entries) => {
  for (let entry of entries) {
    /*
     * 检测节点是否可见,可以通过 isIntersecting 验证,布尔值
     * 如果目标元素与交叉区域观察者对象(intersection observer)的根相交,返回 true
     * 此时描述了变换到交叉时的状态; 
     * 如果返回 false, 那么可以由此判断,变换是从交叉状态到非交叉状态.
     * 同时也可以根据 intersectionRatio 验证
     * intersectionRatio:目标元素的可见比例
     * 即 intersectionRect 占 boundingClientRect 的比例
     * 完全可见时为1,完全不可见时小于等于0
     * 如果不可见,就返回:
     *   if (entry.intersectionRatio <= 0) return;
     */
    if (entry.isIntersecting === true) { // 检测节点是否可见
      // 处于交叉可见状态
      let $image = entry.target as HTMLImageElement;
      $image.src = $image.getAttribute('lazy-src') as string;
      $image.removeAttribute('lazy-src');
    }
  }
});

for (let i = 0, len = $images.length; i < len; i++) {
  // 往观察器列表注册观察元素
  observer.observe($images[i]);
}

参考:
阮一峰:IntersectionObserver API 使用教程

上一篇:Spark on Kubernetes与阿里云的深度整合


下一篇:hibernate之xml映射文件关系维护,懒加载,级联