搭建图片懒加载场景

JavaScript手写专题——图片懒加载、滚动节流、防抖手写-LMLPHP

可以设置这样一个html页面

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Lazy-Load</title>
    <style>
      .container {
        display: flex;
        flex-wrap: wrap;
      }
      .img {
        width: 400px;
        height: 400px;
        margin: 10px;
        background: gray;
      }
    </style>
  </head>
  <body>
    <div class="container">
      <div class="img">
        <img alt="加载中1" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中2" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中3" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中4" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中5" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中6" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中7" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中8" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中9" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中10" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中11" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中12" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中13" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中14" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中15" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中16" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中17" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中18" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中19" class="pic" data-src="./images/image.avif" />
      </div>
      <div class="img">
        <img alt="加载中20" class="pic" data-src="./images/image.avif" />
      </div>
    </div>
  </body>
</html>

懒加载计算滚动到可视窗口

当前可视区域的高度, 在和现代浏览器及 IE9 以上的浏览器中,可以用 window.innerHeight 属性获取。在低版本 IE 的标准模式中,可以用 document.documentElement.clientHeight 获取,这里我们兼容两种情况:

const viewHeight = window.innerHeight || document.documentElement.clientHeight 

元素距离可视区域顶部的高度,我们这里选用 getBoundingClientRect() 方法来获取返回元素的大小及其相对于视口的位置。对此 MDN 给出了非常清晰的解释:

 lazyload方法

<script>
    // 获取所有的图片标签
    const imgs = document.getElementsByTagName('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>

加载效果图

 JavaScript手写专题——图片懒加载、滚动节流、防抖手写-LMLPHP

 节流throttle优化懒加载

throttle 的中心思想在于:在某段时间内,不管你触发了多少次回调,我都只认第一次,并在计时结束时给予响应。 

  function throttle(fn, delay) {
    let last = 0;
    return function () {
      let args = arguments;
      let now = +new Date();
      if (now - last >= delay) {
        fn.apply(this, args);
        last = now;
      }
    };
  }
  function throttle2(fn, delay) {
    let timer = null;
    return function () {
      let context = this;//记住this
      let args = arguments;//参数
      if (!timer) {
        timer = setTimeout(() => {
          fn.apply(context, args);//执行fn
          timer = null;
        }, delay);
      }
    };
  }
  const throttleScroll = throttle(lazyLoad, 3000);
  window.addEventListener("scroll", throttleScroll);

节流效果

JavaScript手写专题——图片懒加载、滚动节流、防抖手写-LMLPHP

实现防抖debounce

 function debounce(fn, delay) {
    let timer = null;
    return function () {
      let context = this;
      let args = arguments;
      clearTimeout(timer); //每次都清空定时器
      timer = setTimeout(() => {
        //定时器执行fn
        fn.apply(context, args);
      }, delay);
    };
  }

 鼠标滚动添加防抖效果

  const debounceScroll = debounce(lazyLoad, 1000);
  window.addEventListener("scroll", debounceScroll);

防抖效果

JavaScript手写专题——图片懒加载、滚动节流、防抖手写-LMLPHP

为了避免弄巧成拙,我们需要借力 throttle 的思想,打造一个“有底线”的 debounce——等你可以,但我有我的原则:delay 时间内,我可以为你重新生成定时器;但只要delay的时间到了,我必须要给用户一个响应。这个 throttledebounce “合体”思路,已经被很多成熟的前端库应用到了它们的加强版 throttle 函数的实现中

有底线的防抖——防抖和节流结合体

function debounce2(fn, delay) {
    let last = 0;
    let timer = null;
    return function (...args) {
      const context = this;
      const now = +new Date();
      if (now - last < delay) {
        //防抖
        clearTimeout(timer);
        timer = setTimeout(() => {
          fn.apply(context, args);
          last = now;
        }, delay);
      } else {
        fn.apply(context, args);
        last = now;
      }
    };
  }

 效果如下,即使鼠标滚动没有停止,到了指定时间一定会执行JavaScript手写专题——图片懒加载、滚动节流、防抖手写-LMLPHP

05-10 09:42