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

34次阅读

共计 6304 个字符,预计需要花费 16 分钟才能阅读完成。

本文在 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 插件原理剖析
webpack 的异步加载原理及分包策略
总结 18 个 webpack 插件,总会有你想要的!
搭建一个 vue-cli4+webpack 挪动端框架(开箱即用)
从零构建到优化一个相似 vue-cli 的脚手架
封装一个 toast 和 dialog 组件并公布到 npm
从零开始构建一个 webpack 我的项目
总结几个 webpack 打包优化的办法
总结 vue 常识体系之高级利用篇
总结 vue 常识体系之实用技巧
总结 vue 常识体系之根底入门篇
总结挪动端 H5 开发罕用技巧(干货满满哦!)

正文完
 0