携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情
前言
图片懒加载是比较常见的优化需求, 在实际开发中, 基本上都是使用其他开发者事先封装好的相关程序.
恰逢有空, 就决定自己手写一个.
基本思路
图片懒加载的原理十分简单, 基本上就是先不要给图片设置 src, 然后监听浏览器的滚动, 当 滚动距离 + 可视区域 > 图片的相对文档高度 时, 再设置图片元素的 src.
效果
实现
img处理
为了方便使用, 可以给 img 标签添加一个特殊的标识, 用于获取需要懒加载的图片, 同时, 为了方便得到图片的 src, 也可以将 src 存储在 img 标签上.
在本文中, 将懒加载的标识与图片地址的存储合为一个 dataset 属性, 名为data-lazy-src:
<img data-lazy-src="https://tse1-mm.cn.bing.net/th/id/OIP-C.SFXnRiI3y-8vGyVwlpeh2gHaLR?pid=ImgDet&rs=1" />
JS 实现
定义管理器
本文中使用一个 JSON 作为管理器, 其中懒加载需要的属性和方法, 定义如下:
const lazyLoadImg = {
// 懒加载标识
lazyFlag: 'data-lazy-src',
// 存储地址的 dataset 字段
dataField: 'lazySrc',
// 图片元素列表
imgList: [],
// 监听滚动防抖定时器句柄
debounceTimer: 0,
// 滚动处理函数
_handleScroll: () => {},
/** 更改懒加载标识 */
changeLazyFlag (flag, field) {},
/** 挂载懒加载 */
mount () {},
/** 卸载懒加载 */
unmount () {},
/** 挂载滚动监听 */
_mountScrollHander () {},
/**
* 卸载滚动监听
* @param { boolean } loadAllImg 是否加载剩余的图片
* */
_unmoutScrollHander (loadAllImg = true) {},
/** 滚动处理函数 */
_createHandleScroll () {},
/** 获取符合要求的 img 元素 */
getLazyImgList () {}
}
对上面这些方法和属性, 主要可以分成两个个部分:
- 获取需要懒加载的
img元素;- 滚动的监听处理
详细看下面的实现.
获取 img 元素
获取元素由 lazyLoadImg.getLazyImgList 实现, 主要通过元素的懒加载标识 lazyFlag 进行获取, 然后创建对应的列表,存储在 lazyLoadImg.imgList 中:
/** 获取符合要求的 img 元素 */
getLazyImgList () {
// 获取存储 src dataset属性
const dataField = this.dataField
// 通过懒加载标识 `lazyFlag` 进行获取元素
this.imgList = [...document.querySelectorAll(`img[${this.lazyFlag}]`)].map(item => {
// 为了方便区分执行懒加载, 使用时间作为 title 区分, 实际开发中不需要
item.title = (new Date()).toLocaleString()
// 返回记录 img 信息的 JSON
return {
// 图片元素
element: item,
// 图片相对与文档的高度
top: item.offsetTop,
// 图片的地址
src: item.dataset[dataField]
}
})
}
上面有两个标识:
一个是dateField, 用于获取放在图片元素的dataset上的图片地址;
一个是lazyFlag, 懒加载的标识;
滚动的监听处理
首先, 利用闭包创建一个滚动的处理函数 _handleScroll, 因为监听需要可以取消, 所以必须保证 addEventListener 和 removeEventListener 是同一个函数, 才能正确进行取消.
但是使用 addEventListener 挂载后, this 会指向挂载的元素, 所以得用闭包记录下 lazyLoadImg 的 this, 方便调用 lazyLoadImg 的数据和函数.
同时, 由于滚动一般都是连续的, 所以加一个防抖处理, 节约资源:
/** 生成滚动处理函数 */
_createHandleScroll () {
const _this = this
this._handleScroll = function () {
// 防抖处理
if (_this.debounceTimer) {
clearTimeout(_this.debounceTimer)
}
this.debounceTimer = setTimeout(() => {
const { innerHeight, scrollY } = window
// 计算当前可视区域 + 滚动过的高度
const viewHeight = innerHeight + scrollY
const dataField = _this.dataField
console.log(1)
// 遍历, 处理符合条件的图片进行展示, 不符合的继续留在 imgList 中
_this.imgList = _this.imgList.reduce((newList, item) => {
console.log('newList:',newList)
if (item.top <= viewHeight) {
// 符合条件的图片, 设置上正确的地址
item.element.src = item.src
item.element.title = (new Date()).toLocaleString()
delete item.element.dataset[dataField]
} else {
newList.push(item)
}
return newList
}, [])
// 如果不存在懒加载的图片, 卸载滚动监听事件
if (_this.imgList.length <= 0) {
_this._unmoutScrollHander()
}
}, 200)
}
}
然后就是挂载监听滚动事件以及卸载监听滚动事件:
/** 挂载滚动监听 */
_mountScrollHander () {
window.addEventListener('scroll', this._handleScroll)
},
/**
* 卸载滚动监听
* @param { boolean } loadAllImg 是否加载剩余的图片
* */
_unmoutScrollHander (loadAllImg = true) {
window.removeEventListener('scroll', this._handleScroll)
// 将未加载的图片,一次性加载完成
if (this.imgList.length > 0 && loadAllImg) {
this.imgList.forEach((item) => {
item.element.src = item.src
item.element.title = (new Date()).toLocaleString()
delete item.element.dataset[dataField]
})
}
},
注意: 卸载时,需要对剩下未加载的图片进行处理, 默认直接进行加载.
启动 lazyLoaImg
上面已经实现了基本的流程, 接下来就是实现一个启动的函数:
/** 开始挂载懒加载 */
mount () {
// 获取元素
this.getLazyImgList()
// 创建滚动处理函数
this._createHandleScroll()
// 挂载监听事件
this._mountScrollHander()
// 检测当是否存在需要加载的图片
this._handleScroll()
},
接着使用就很简单了, 代码如下:
// 导入
import lazyLoadImg from 'xsxxx.js'
// 启动
lazyLoadImg.mount()