页面性能优化-原生JS实现图片懒加载(lazyLoad)

522 阅读3分钟

前言

在说懒加载之前先明白hrefsrc的区别:
        href标识超文本引用,用在linka等元素上,href是引用和页面关联,是在当前元素和引用资源之间建立联系。
        src表示引用资源,表示替换当前元素,用在imgscriptiframe上,src是页面内容不可缺少的一部分。
        srcsource的缩写,是指向外部资源的位置,指向的内部会迁入到文档中当前标签所在的位置;在请求src资源时会将其指向的资源下载并应用到当前文档中,例如js脚本,img图片和frame等元素。         当浏览器解析到这一句的时候会暂停其他资源的下载和处理,直至将该资源加载,编译,执行完毕,图片和框架等元素也是如此,类似于该元素所指向的资源嵌套如当前标签内,这也是为什么要把js放在底部而不是头部。 在项目开发中,我们往往会遇到一个页面需要加载很多图片的情况。我们如果一次性全部加载图片,如果首页图片过大过多,就导致白屏现象严重,所以我们需要对图片加载进行优化,图片懒加载也是比较常见的一种性能优化的方法。

懒加载原理

<img>标签有一个属性是 src,用来表示图像的URL,当这个属性的值不为空时,浏览器就会根据这个值发送请求。如果没有 src属性,就不会发送请求。所以大概思路为两个

  1. 给图片img标签自定义一个属性data-src来存放真实的地址。
  2. 当滚动页面时,检查所有的img标签,判断是否出现在视野中,如果出现在视野中,继续进行判断,看是否被加载过了,如果没有加载,那就进行加载。

那么问题来了如何判断元素是否在可视区域?
这里需要用到三个属性

document.documentElement.clientHeight;//获取可视区域高度
document.documentElement.scrollTop; // 页面向上滚动的高度
img.offsetTop //目标标签相对于document的高度

具体效果是这样的看图

 // 判断img是否出现浏览器视野中,并且没有被加载过
function isShow(img) {
  const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  const windowHeight = document.documentElement.clientHeight;
  const offsetTop = img.offsetTop
  return (offsetTop < (windowHeight + scrollTop) && !img.getAttribute('data-loading'))
}

当然涉及到滚动之类的高并发的事件都要进行防抖和节流
然而在这里用那个都显得不尽如意,所以借鉴大神的写法,把两者结合

function throttle(fn, delay) {
    // oldTime为上一次触发回调的时间, timer是定时器
    let oldTime = 0, timer = null
    // 将throttle处理结果当作函数返回
    return function () {
      // 记录本次触发回调的时间
      let nowTime = +new Date()
      // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
      if (nowTime - oldTime < delay) {
        // 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
        clearTimeout(timer)
        timer = setTimeout(function () {
          oldTime = nowTime
          fn()
        }, delay)
      } else {
        // 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应
        oldTime = nowTime
        fn()
      }
    }
  }

全部代码如下:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    img {
      width: 400px;
      display: block;
      margin: 0 auto;
      height: 300px;
    }
  </style>
</head>

<body>
  <img data-src="1.jpg" alt="loading">
  <img data-src="2.jpg" alt="loading">
  <img data-src="3.jpg" alt="loading">
  <img data-src="4.jpg" alt="loading">
  <img data-src="5.jpg" alt="loading">
  <img data-src="6.jpg" alt="loading">
  <img data-src="7.jpg" alt="loading">
  <img data-src="9.jpg" alt="loading">
  <img data-src="10.jpg" alt="loading">
</body>
<script>
  const imgs = document.querySelectorAll('img');
  const len = imgs.length

  function isShow(img) {
    const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
    const windowHeight = document.documentElement.clientHeight;
    const offsetTop = img.offsetTop
    //判断当前img是否出现了在视野中。
    return offsetTop < (windowHeight + scrollTop)
  }
  function throttle(fn, delay) {
    // oldTime为上一次触发回调的时间, timer是定时器
    let oldTime = 0, timer = null
    // 将throttle处理结果当作函数返回
    return function () {
      // 记录本次触发回调的时间
      let nowTime = +new Date()
      // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值
      if (nowTime - oldTime < delay) {
        // 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器
        clearTimeout(timer)
        timer = setTimeout(function () {
          oldTime = nowTime
          fn()
        }, delay)
      } else {
        // 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应
        oldTime = nowTime
        fn()
      }
    }
  }
  function lazyLoad() {
    let count = 0;
    //利用闭包来保存一个变量,每次记录更换src的最终位置,这样就不用每次都全部遍历
    return () => {
      for (var i = count; i < len; i++) {
        //如果img到达视野内
        if (isShow(imgs[i])) {
          imgs[i].src = imgs[i].getAttribute('data-src');
          //把img的src换成data-src里面的真实地址,并且记录下最后换到那个位置,
          count = i;
        }
      }
    }
  }
  //用变量来接收lazyLoad运行结果
  let lazy = lazyLoad()
  //首页加载刚进去需要加载一下
  lazy()
  window.addEventListener('scroll', throttle(lazy, 300), false)
</script>

</html>