图片懒加载的实践方案
2020.05.12图片懒加载介绍
图片懒加载是非常常见的功能需求,其目的就是为了提升应用性能防止浏览的浪费。其本质功能就是图片进入用户的可视或者预可视(可视区域+buffer距离)区域才回去加载对应的图片
监听页面的滚动
实现方案
方案非常简单,就是监听window.scroll事件,在事件里判断图片是否已经进入可视(预可视)
注意点
window.scroll只能监听body滚动的事件,因此列表设计样式的时候要让body成为滚动的区域- 避免
window.scroll事件的覆盖,每个组件中都会去监听window.scroll事件,要保留之前window.scroll的逻辑。
const onscroll = window.onscroll || function() {}
window.onscroll = throttle(() => {
onscroll()
this.lazyLoadImg()
}, 1000, true)
- 类似滚动这种高频触发事件需要节流(前端必备常识)
代码如下:
// lazyLoadImg 函数实现
// 已经加载过的就直接返回
if (this.startedLoad) {
return
}
// 已经元素没有进入视口区域,则不加载背景图片
const scrollTop = document.documentElement.scrollTop || document.body.scrollTop // 滚动条距离顶部高度
// 元素的滚动距离 > 视口高度 + body 滚动距离 + 某一常量时触发
if (this.container.offsetTop > screenHeight + scrollTop + BUFFER_HEIGHT) {
return
}
const contentElement = this.$refs.content
if (!contentElement) return
this.startedLoad = true
const image = new Image()
image.src = this.imgUrl
image.onload = () => {
contentElement.style.backgroundSize = 'cover'
contentElement.style.backgroundImage = `url(${this.imgUrl})`
this.loaded = true
}
遗留问题
组件卸载掉后需要还原scroll事件,但是原来的事件早就已经被覆盖掉很难找回了,除非一开始就给它预留好,但是这样做代码逻辑就会变得很繁琐。
使用 IntersectionObserver 代替 window.scroll
使用 IntersectionObserver 方案可以避免方案一带来的问题,IntersectionObserver 是 Html5 新出的API,本身的作用就是用来监测元素是否在浏览器的可视区域。
this.container = this.$refs.sectionContainer
const observer = new IntersectionObserver(
(changes) => {
changes.forEach((change) => {
const { boundingClientRect: { y: offsetTop }, rootBounds: { height: screenHeight }} = change
if (offsetTop < screenHeight + BUFFER_HEIGHT) {
this.lazyLoadImg()
// 加载完成关闭观察器
observer.disconnect()
}
})
}
)
observer.observe(this.$refs.sectionContainer)
lazyLoadImg 函数也会变得更简单
const contentElement = this.$refs.content
if (!contentElement) return
this.startedLoad = true
const image = new Image()
image.src = this.imgUrl
image.onload = () => {
contentElement.style.backgroundSize = 'cover'
contentElement.style.backgroundImage = `url(${this.imgUrl})`
this.loaded = true
}
注意的点
发现 IntersectionObserver 这个 api 有个坑,就是在移动 boundingClientRect 对象中并没有 x 这个属性, 需要用 top 属性代替,而且 IntersectionObserver 的支持度不是很好,目前的做法是实现降级处理,如果不支持就采用 scroll 的方法。
当然也可以引入相应的 polyfill (https://polyfill.io/v2/polyfill.min.js?features=IntersectionObserver)。