前端性能优化-图片懒加载(防抖、节流)的几种方案

  • 时间:
  • 浏览:2535
  • 来源:无双科技

懒加载使用场景

在一些图片量比较大的场景(电商首页,小程序首页等),如果我们打开页面时就加载所有的图片,那势必会导致页面的卡顿以及白屏,给用户不好的体验,导致用户流失。

但是我们仔细想一下,用户真的需要我们显示所有图片一起展示吗?其实并不是,用户看到的只是浏览器可视区域的内容。所以从这个情况我们可以做一些优化,只显示用户可视区域内的图片,当用户触发滚动的瞬间再去请求显示给用户。

前端性能优化-图片懒加载(防抖、节流)的几种方案

懒加载的思路

  • img 标签有自定义属性 data-src
  • 首屏展示可视区域内的图片 src 值 替换为 data-src
  • 滚动出现在可视区域的图片即时展示 (重复第二步)

    那么我们在实际应用中如何判断图片出现在了当前视口 (即如何判断我们能够看到图片),如何控制图片的加载呢?

方案一:如何判断图片出现在了当前视口

clientTop,offsetTop,clientHeight 以及 scrollTop 各种关于图片的高度作比对

这些高度都代表了什么意思?

这我以前有可能是知道的,那时候我比较单纯,喜欢死磕。我现在想通了,背不过的东西就不要背了

所以它有一个问题:复杂琐碎不好理解!

仅仅知道它静态的高度还不够,我们还需要知道动态的

如何动态?监听 window.scroll 事件

如何控制图片的加载

<img data-src="shanyue.jpg">

首先设置一个临时属性 data-src,控制加载时使用 src 代替 data-src

方案二:如何判断图片出现在了当前视口

引入一个新的 API, Element.getBoundingClientRect() 方法返回元素的大小及其相对于视口的位置。

那如何判断图片出现在了当前视口呢,根据示例图示意,代码如下,这个就比较好理解了,就可以很容易地背会(就可以愉快地去面试了)。

// clientHeight 代表当前视口的高度
img.getBoundingClientRect().top < document.documentElement.clientHeight

监听 window.scroll 事件也优化一下

加个节流器,提高性能。工作中一般使用 lodash.throttle 就可以了,万能的 lodash 啊!

_.throttle(func, [wait=0], [options={}])

参考 什么是防抖和节流,他们的应用场景有哪些

方案三:如何判断图片出现在了当前视口

方案二使用的方法是: window.scroll 监听 Element.getBoundingClientRect() 并使用 _.throttle 节流

一系列组合动作太复杂了,于是浏览器出了一个三合一事件: IntersectionObserver API,一个能够监听元素是否到了当前视口的事件,一步到位!

事件回调的参数是 IntersectionObserverEntry 的集合,代表关于是否在可见视口的一系列值

其中,entry.isIntersecting 代表目标元素可见

const observer = new IntersectionObserver((changes) => {
// changes: 目标元素集合
changes.forEach((change) => {
// intersectionRatio
if (change.isIntersecting) {
const img = change.target
img.src = img.dataset.src
observer.unobserve(img)
}
})
})

observer.observe(img)

当然,IntersectionObserver 除了给图片做懒加载外,还可以对单页应用资源做预加载。

如在 next.js v9 中,会对视口内的资源做预加载,可以参考 next 9 production optimizations

<Link href="/about">
<a>关于山月</a>
</Link>

方案四:

浏览器觉得懒加载这事可以交给自己做,你们开发者加个属性就好了。实在是...!

<img src="shanyue.jpg" loading="lazy">

不过目前浏览器兼容性不太好

防抖 && 节流

防抖:在一定时间内,触发多次事件,只认第一次触发的,到了时间结束执行事件

 function throttle(fn, time) {
let oldTime = 0,
timer = null;
return () => {
const nowTime = new Date()
if (nowTime - oldTime >= time) {
fn()
oldTime = nowTime
}
}
}
复制代码

节流:在一定时间内,触发多次事件,只认最后一次触发的并且重置时间,到了时间结束执行事件

function debounce(fn, time) {
let timer = null
return () => {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn()
}, time);
}
}
复制代码

在这里debounce有一个严重的问题就是如果用户一直触发事件,用户会一直得不到响应,所以我们可以借助防抖的思路来优化节流。

 function debounce(fn, time) {
let oldTime = 0,
timer = null;
return () => {
const nowTime = new Date()
if (nowTime - oldTime < time) {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
oldTime = nowTime
fn()
}, time);
} else {
// 用户重复触发,到达事件节点 还是会去执行事件
oldTime = nowTime
fn()
}
}
}
复制代码

所以最后我们监听事件可以修改为containr.addEventListener('scroll', debounce(load, 1000), false),这就完美达到了我们优化的目的。


总结

总结一下

  • window.scroll 监听各种 top 与 height 并使用 _.throttle 节流,但是不好理解各种 top 与 hegith
  • window.scroll 监听 getBoundingClientRect 并使用 _.throttle 节流,没有一个统一事件,相对复杂
  • IntersectionObserver,浏览器推出了一个事件,方便简单
  • img.loading=lazy,浏览器直接给你解决,开发者直接标注属性

猜你喜欢