Lazy-Load的实现思路| 青训营笔记

86 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 13 天

懒加载

顾名思义,就是图片进入可视区再加载图片。涉及的知识点有节流、IntersectionObserver

通过事件实现

主要是获取当前的可视区域高度和元素距离可视区域顶部的高度,假设我们这里有一些图片

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Lazy-Load</title>
  <style>
    .img {
      width: 200px;
      height:200px;
      background-color: gray;
    }
    .pic {
      // 必要的img样式
    }
  </style>
</head>
<body>
  <div class="container">
    <div class="img">
      // 注意我们并没有为它引入真实的src
      <img class="pic" alt="加载中" data-src="./images/1.png">
    </div>
    <div class="img">
      <img class="pic" alt="加载中" data-src="./images/2.png">
    </div>
    <div class="img">
      <img class="pic" alt="加载中" data-src="./images/3.png">
    </div>
    <div class="img">
      <img class="pic" alt="加载中" data-src="./images/4.png">
    </div>
    <div class="img">
      <img class="pic" alt="加载中" data-src="./images/5.png">
    </div>
     <div class="img">
      <img class="pic" alt="加载中" data-src="./images/6.png">
    </div>
     <div class="img">
      <img class="pic" alt="加载中" data-src="./images/7.png">
    </div>
     <div class="img">
      <img class="pic" alt="加载中" data-src="./images/8.png">
    </div>
     <div class="img">
      <img class="pic" alt="加载中" data-src="./images/9.png">
    </div>
     <div class="img">
      <img class="pic" alt="加载中" data-src="./images/10.png">
    </div>
  </div>
</body>
</html>
const viewHeight = window.innerHeight || document.documentElement.clientHeight 

document.documentElement主要是为了兼容低版本的ie,如果不需要,直接用window获取也行

那元素的距离可视顶部的高度如何获取呢?我们可以利用 getBoundingClientRect() 来获取,也可以通过获取dom元素,然后获取他的offsetTop。

getBoundingClientRect().topoffsetTop 都是 JavaScript 中用于获取 DOM 元素顶部相对于页面顶部或父元素顶部的距离的方法。但是,它们的工作方式和结果略有不同。前者是相当于视口的顶部,而后者是相对于父元素的顶部,所以说,用getBoundingClientRect更加合适。

这里我们我们来实现一下

<script>
    // 获取所有的图片标签
    const imgs = document.querySelector('img')
    // 获取可视区域的高度
    const viewHeight = window.innerHeight || document.documentElement.clientHeight
    // num用于统计当前显示到了哪一张图片,避免每次都从第一张图片开始检查是否露出
    let num = 0
    function lazyload(){
        for(let i=num; i< imgs.length; i++) {
            // 用可视区域高度减去元素顶部距离可视区域顶部的高度
            let distance = viewHeight - imgs[i].getBoundingClientRect().top
            // 如果可视区域高度大于等于元素顶部距离可视区域顶部的高度,说明元素露出
            if(distance >= 0 ){
                // 给元素写入真实的src,展示图片
                imgs[i].src = imgs[i].getAttribute('data-src')
                // 前i张图片已经加载完毕,下次从第i+1张开始检查是否露出
                num = i + 1
            }
        }
    }
    // 监听Scroll事件
    window.addEventListener('scroll', lazyload, false);
</script>

IntersectionObserver API

Intersection Observer API 提供了一种异步检测目标元素与祖先元素或 viewport 相交情况变化的方法。

下面注册全局自定义指定v-lazyload

Vue.directive('lazyload', {
  bind: function (el, bindVal) {
    const lazyLoadObser = new IntersectionObserver((entries, observer) => {
      entries.forEach((entry, index) => {
        const lazyImg = entry.target
        // intersectionRatio:目标元素的可见比例,
        // 即intersectionRect占boundingClientRect的比例,
        // 完全可见时为1,完全不可见时小于等于0
        if (entry.intersectionRatio > 0) {
          lazyImg.src = bindVal.value
          console.log(111)
          lazyLoadObser.unobserve(lazyImg)
        }
      })
    })
    lazyLoadObser.observe(el)
  }
})
​

原生js写法

function query(selector) {
  return Array.from(document.querySelectorAll(selector));
}
​
var observer = new IntersectionObserver(
  function(changes) {
    changes.forEach(function(change) {
      var container = change.target;
      var content = container.querySelector('template').content;
      container.appendChild(content);
      observer.unobserve(container);
    });
  }
);
​
query('.lazy-loaded').forEach(function (item) {
  observer.observe(item);
});

上面代码中,只有目标区域可见时,才会将模板内容插入真实 DOM,从而引发静态资源的加载。

参考资料