此功能函数的特色
- 由于图片懒加载需要监听滚动条事件,而滚动条事件触发非常频繁,因此函数内部做了众多优化
- 无论滚动条是 html 的、 body 的,或者某个容器的,都能通过此函数实现懒加载功能
- 使用简单:只需调用 $lazyloadFn(<传入滚动条元素节点>) 即可实现
使用方法
- 在 index.html 的 script 标签中添加下面的代码,把 $lazyloadFn 添加为 window 的方法
- img 标签的 data-src 属性添加真实图片地址,src 属性添加 loading 图片地址
- 图片地址请求回来并且添加到 img 的 data-src 之后调用 $lazyloadFn(<传入滚动条元素节点>)
- 滚动条是 html 或者 body 时,<传入滚动条元素节点> 为 window,如果图片在某个容器里滚动的,则传入这个容器节点
- 注意如果传入滚动条元素节点不是 window 则滚动条元素须开启定位,且滚动条元素到 img 标签之间的元素不能开启定位
图片懒加载实现的思路
使用闭包把变量保存起来
定义两个函数 lazyload forFn
lazyload 用于绑定滚动条监听:
判断如果滚动条往回滚则退出lazyload,
使用函数防抖,使滚动条停止后才触发 forFn
forFn 用于遍历计算图片是否可见:
把已经显示真实地址的下标保存起来,遍历图片的时候是从这个下标开始遍历的
当遍历到图片不可见时,那么它之后的图片都是不可见,也就不需要再遍历了
如果全部图片都显示真实地址了则删除滚动条监听
这里使用到了闭包,顺便说下本人对闭包的理解:
- 函数调用时,内部变量的值都会被重置,使用闭包可以将变量的值保存起来,避免被重置
- 函数内部的方法使用外层函数的变量,当调用内部方法时就会产生闭包
- 通常函数只调用一次,调用之后的结果分为两种:
- return 出去一个方法,之后需要再次调用时,我们调用的是返回的这个方法
- 把函数内部的方法绑定给事件监听,之后事件触发的是这个方法
- 这样函数内部(注意不是方法内部)变量的值就会被保存起来
// 把 $lazyloadFn 添加为 window 的方法,方便之后调用
window.$lazyloadFn = function (el) {
if(el === undefined) return
// 保存已经显示真实图片地址的下标
let index = 0
// 保存滚动条滚动的最远的距离
let oldScroll = 0
// 函数防抖延时器 id
let timeId
// 添加滚动条监听
el.addEventListener('scroll',lazyload)
// 手动触发1次遍历图片函数
forFn()
// 滚动条事件触发函数
function lazyload(){
// scrollTop 是 DOM 节点滚动条的滚动距离。
// scrollY 是 window 的属性,scrollY 是 html 或者 body 滚动条的滚动距离
// 下面这个语句表示如果滚动条往回滚则退出函数
if((el.scrollTop || el.scrollY) < oldScroll) return
// 如果向前滚动距离超过 300 则遍历图片是否可见
if((el.scrollTop || el.scrollY) - oldScroll > 300) forFn()
// 函数防抖,滚动条停止后才会遍历图片
clearInterval(timeId)
timeId = setTimeout(forFn,200)
}
// 遍历图片是否可见的函数
function forFn() {
// 获取添加了 data-src 属性的 DOM 节点
let imgs = document.querySelectorAll('[data-src]')
// 如果没有获取到则退出函数
if(imgs.length === 0) return
// 如果滚动条是向前滚动则保存当前的滚动距离
if((el.scrollTop || el.scrollY) > oldScroll) oldScroll = el.scrollTop || el.scrollY
// 如果所有图片已经显示真实地址则删除滚动条监听
if(index === imgs.length) el.removeEventListener('scroll',lazyload)
// 判断 el 是 window 还是 dom 节点,他们计算图片是否可见的方式不一样
if(el === window){
// 遍历图片,计算图片是否可见,从未显示真实地址的图片开始遍历
for (let i=index,length=imgs.length; i<length; i++){
if(imgs[i].getBoundingClientRect().top-window.innerHeight<0){
// 如果图片可见则将 data-src 中的真实图片地址保存到 src 中
imgs[i].src = imgs[i].getAttribute("data-src")
// 保存已经显示真实地址的下标
index++
// 如果遍历到图片不可见,那么它之后的图片都是不可见,则退出遍历
}else break
}
}else {
// 这里是 el 为 dom 节点的遍历逻辑,和上面的遍历大致相同
for (let i=index,length=imgs.length; i<length; i++){
if(imgs[i].offsetTop <= el.clientHeight + el.scrollTop){
imgs[i].src = imgs[i].getAttribute("data-src")
index++
}else break
}
}
}
}