最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 前端性能优化之图片懒加载

    正文概述 掘金(lzg9527)   2020-12-08   337

    本文在github做了收录 github.com/Michael-lzg…

    demo源码地址 github.com/Michael-lzg…

    在类电商类项目,往往存在大量的图片,如 banner 广告图,菜单导航图,美团等商家列表头图等。图片众多以及图片体积过大往往会影响页面加载速度,造成不良的用户体验,所以进行图片懒加载优化势在必行。

    为什么要进行图片懒加载

    我们先来看一下页面启动时加载的图片信息。

    前端性能优化之图片懒加载

    如图所示,这个页面启动时加载了几十张图片(甚至更多),而这些图片请求几乎是并发的,在 Chrome 浏览器,最多支持的并发请求次数是有限的,其他的请求会推入到队列中等待或者停滞不前,直到上轮请求完成后新的请求才会发出。所以相当一部分图片资源请求是需要排队等待时间的。

    在上面可以看出,有部分图片达到几百 kB,设置 2M(这锅必须运营背,非得上传高清大图不可?),直接导致了加载时间过长。

    前端性能优化之图片懒加载

    针对以上情况,进行图片懒加载有以下优点:

    1. 减少资源的加载,页面启动只加载首屏的图片,这样能明显减少了服务器的压力和流量,也能够减小浏览器的负担。
    2. 防止并发加载的资源过多而阻塞 js 的加载,影响整个网站的启动。
    3. 能提升用户的体验,不妨设想下,用户打开页面的时候,如果页面上所有的图片都需要加载,由于图片数目较大,等待时间很长这就严重影响用户体验。

    图片懒加载的原理

    图片懒加载的原理主要是判断当前图片是否到了可视区域这一核心逻辑实现的

    1. 拿到所有的图片 dome 。
    2. 遍历每个图片判断当前图片是否到了可视区范围内。
    3. 如果到了就设置图片的 src 属性。
    4. 绑定 window 的 scroll 事件,对其进行事件监听。

    我们先来看下页面结构

    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <title>Lazyload</title>
        <style>
          img {
            display: block;
            margin-bottom: 50px;
            height: 200px;
            width: 400px;
          }
        </style>
      </head>
      <body>
        <img src="./img/default.png" data-src="./img/1.jpg" />
        <img src="./img/default.png" data-src="./img/2.jpg" />
        <img src="./img/default.png" data-src="./img/3.jpg" />
        <img src="./img/default.png" data-src="./img/4.jpg" />
        <img src="./img/default.png" data-src="./img/5.jpg" />
        <img src="./img/default.png" data-src="./img/6.jpg" />
        <img src="./img/default.png" data-src="./img/7.jpg" />
        <img src="./img/default.png" data-src="./img/8.jpg" />
        <img src="./img/default.png" data-src="./img/9.jpg" />
        <img src="./img/default.png" data-src="./img/10.jpg" />
      </body>
    </html>
    

    先获取所有图片的 dom,通过 document.body.clientHeight 获取可视区高度,再使用 element.getBoundingClientRect() API 直接得到元素相对浏览的 top 值, 遍历每个图片判断当前图片是否到了可视区范围内。代码如下:

    function lazyload() {
      let viewHeight = document.body.clientHeight //获取可视区高度
      let imgs = document.querySelectorAll('img[data-src]')
      imgs.forEach((item, index) => {
        if (item.dataset.src === '') return
    
        // 用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置
        let rect = item.getBoundingClientRect()
        if (rect.bottom >= 0 && rect.top < viewHeight) {
          item.src = item.dataset.src
          item.removeAttribute('data-src')
        }
      })
    }
    

    最后给 window 绑定 onscroll 事件

    window.addEventListener('scroll', lazyload)
    

    主要就完成了一个图片懒加载的操作了。但是这样存在较大的性能问题,因为 scroll 事件会在很短的时间内触发很多次,严重影响页面性能,为了提高网页性能,我们需要一个节流函数来控制函数的多次触发,在一段时间内(如 200ms)只执行一次回调。

    下面实现一个节流函数

    function throttle(fn, delay) {
      let timer
      let prevTime
      return function (...args) {
        const currTime = Date.now()
        const context = this
        if (!prevTime) prevTime = currTime
        clearTimeout(timer)
    
        if (currTime - prevTime > delay) {
          prevTime = currTime
          fn.apply(context, args)
          clearTimeout(timer)
          return
        }
    
        timer = setTimeout(function () {
          prevTime = Date.now()
          timer = null
          fn.apply(context, args)
        }, delay)
      }
    }
    

    然后修改一下 srcoll 事件

    window.addEventListener('scroll', throttle(lazyload, 200))
    

    IntersectionObserver

    通过上面例子的实现,我们要实现懒加载都需要去监听 scroll 事件,尽管我们可以通过函数节流的方式来阻止高频率的执行函数,但是我们还是需要去计算 scrollTopoffsetHeight 等属性,有没有简单的不需要计算这些属性的方式呢,答案就是 IntersectionObserver

    IntersectionObserver 是一个新的 API,可以自动"观察"元素是否可见,Chrome 51+ 已经支持。由于可见(visible)的本质是,目标元素与视口产生一个交叉区,所以这个 API 叫做"交叉观察器"。我们来看一下它的用法:

    var io = new IntersectionObserver(callback, option)
    
    // 开始观察
    io.observe(document.getElementById('example'))
    
    // 停止观察
    io.unobserve(element)
    
    // 关闭观察器
    io.disconnect()
    

    IntersectionObserver 是浏览器原生提供的构造函数,接受两个参数:callback 是可见性变化时的回调函数,option 是配置对象(该参数可选)。

    目标元素的可见性变化时,就会调用观察器的回调函数 callback。callback 一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。

    var io = new IntersectionObserver((entries) => {
      console.log(entries)
    })
    

    callback 函数的参数(entries)是一个数组,每个成员都是一个 IntersectionObserverEntry 对象。举例来说,如果同时有两个被观察的对象的可见性发生变化,entries 数组就会有两个成员。

    • time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
    • target:被观察的目标元素,是一个 DOM 节点对象
    • isIntersecting: 目标是否可见
    • rootBounds:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回 null
    • boundingClientRect:目标元素的矩形区域的信息
    • intersectionRect:目标元素与视口(或根元素)的交叉区域的信息
    • intersectionRatio:目标元素的可见比例,即 intersectionRectboundingClientRect 的比例,完全可见时为 1,完全不可见时小于等于 0

    下面我们用 IntersectionObserver 实现图片懒加载

    const imgs = document.querySelectorAll('img[data-src]')
    const config = {
      rootMargin: '0px',
      threshold: 0,
    }
    let observer = new IntersectionObserver((entries, self) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          let img = entry.target
          let src = img.dataset.src
          if (src) {
            img.src = src
            img.removeAttribute('data-src')
          }
          // 解除观察
          self.unobserve(entry.target)
        }
      })
    }, config)
    
    imgs.forEach((image) => {
      observer.observe(image)
    })
    

    懒加载指令

    Vue 中除了平时常用的 v-showv-bindv-for 等指令外,还可以自定义指令。Vue 指令定义函数提供了几个钩子函数(可选):

    • bind: 只调用一次,指令第一次绑定到元素时调用,可以定义一个在绑定时执行一次的初始化动作。
    • inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
    • update: 被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值。
    • componentUpdated: 被绑定元素所在模板完成一次更新周期时调用。
    • unbind: 只调用一次, 指令与元素解绑时调用。

    实现一个懒加载指令的思路

    1. 判断浏览器是否支持 IntersectionObserver API,如果支持就使用 IntersectionObserver 实现懒加载,否则则使用 srcoll 事件监听 + 节流的方法实现。
    2. 通过 Vue.directive 注册一个 v-lazy 的指令,暴露一个 install() 函数,供 Vue 调用。
    3. main.js 里 use(指令) 即可调用。
    4. 将组件内 <img> 标签的 src 换成 v-lazy 即可实现图片懒加载。

    代码如下

    新建 LazyLoad.js 文件

    const LazyLoad = {
      // install方法
      install(Vue, options) {
        const defaultSrc = options.default
        Vue.directive('lazy', {
          bind(el, binding) {
            LazyLoad.init(el, binding.value, defaultSrc)
          },
          inserted(el) {
            if (IntersectionObserver) {
              LazyLoad.observe(el)
            } else {
              LazyLoad.listenerScroll(el)
            }
          },
        })
      },
      // 初始化
      init(el, val, def) {
        el.setAttribute('data-src', val)
        el.setAttribute('src', def)
      },
      // 利用IntersectionObserver监听el
      observe(el) {
        var io = new IntersectionObserver((entries) => {
          const realSrc = el.dataset.src
          if (entries[0].isIntersecting) {
            if (realSrc) {
              el.src = realSrc
              el.removeAttribute('data-src')
            }
          }
        })
        io.observe(el)
      },
      // 监听scroll事件
      listenerScroll(el) {
        const handler = LazyLoad.throttle(LazyLoad.load, 300)
        LazyLoad.load(el)
        window.addEventListener('scroll', () => {
          handler(el)
        })
      },
      // 加载真实图片
      load(el) {
        const windowHeight = document.documentElement.clientHeight
        const elTop = el.getBoundingClientRect().top
        const elBtm = el.getBoundingClientRect().bottom
        const realSrc = el.dataset.src
        if (elTop - windowHeight < 0 && elBtm > 0) {
          if (realSrc) {
            el.src = realSrc
            el.removeAttribute('data-src')
          }
        }
      },
      // 节流
      throttle(fn, delay) {
        let timer
        let prevTime
        return function (...args) {
          const currTime = Date.now()
          const context = this
          if (!prevTime) prevTime = currTime
          clearTimeout(timer)
    
          if (currTime - prevTime > delay) {
            prevTime = currTime
            fn.apply(context, args)
            clearTimeout(timer)
            return
          }
    
          timer = setTimeout(function () {
            prevTime = Date.now()
            timer = null
            fn.apply(context, args)
          }, delay)
        }
      },
    }
    
    export default LazyLoad
    

    main.js 里 use 指令

    import LazyLoad from './LazyLoad.js'
    
    Vue.use(LazyLoad, {
      default: 'xxx.png',
    })
    

    将组件内 <img> 标签的 src 换成 v-lazy

    <img v-lazy="xxx.jpg" />
    

    这样就能完成一个 vue 懒加载的指令了。

    小结

    1. 为提高网站加载性能,图片懒加载是必要的。
    2. 图片懒加载是实现原理是判断当前图片是否到了可视区域进行加载,可通过监听 scroll 事件和 IntersectionObserver 实现相应的功能。
    3. 可通过 Vue.directive 编写图片懒加载指令。

    推荐文章

    w你必须知道的webpack插件原理分析

    起源地下载网 » 前端性能优化之图片懒加载

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元