lazyLoad
顾名思义,是懒加载,对于网页端来说,用户的体验非常重要,如果想要加快用户体验,更早的看到内容,不仅需要网络加速,更需要对大文件进行优化,以期达到快速响应的目的.
今天说的
lazyload是比较常见的前端优化,主要作用于图片的加载
特点是只有当图片进入可视区的时候,才去请求加载
对于图片较多的网站,这样可以大大的加快用户访问速度,增大用户留存度
原理
首先需要一个可以发生滚动的父元素,因为需要绑定scroll事件,进行不断的检测图片是否在可视区域内
- 找可以滚动的元素,即
overflow = scroll | auto - 初始化加载
- 发生滚动时,判断元素是否在
可视区域内 - 当元素已经加载过,如果继续滚动,就要停止加载
- 如果没有加载过,需要设置
src属性
graph TD
Start --> container(寻找可以滚动的元素)
Start --> init(init-初始化加载填充页面)
container == 发生滚动===> check{是否在可视区域内}
check ==是===>isLoad{是否加载过}
isLoad --从未加载-->a(进行加载)
代码
vue 指令初始化
Vue.use(VueLazyload, {
loading: 'http://localhost:3001/images/loading.gif',
error: 'http://localhost:3001/images/error.jpg',
preload: 1 // 距离屏幕多少开始加载
})
VueLazyload
const VueLazyload = {
install (Vue, options) {
const LazyClass = Lazyload(Vue);
const lazyload = new LazyClass(options);
Vue.directive('lazy', {
// el bindings vnode
bind: lazyload.bindLazy.bind(lazyload)
})
}
}
上述代码作为指令的初始化,Vue指令需要一个带有install方法的对象
主要是在Lazyload 函数,接受一个Vue 实例,返回一个lazyClass
在类lazyClass 中接受参数option = {loading:xxx,error:xxx }
这样做的目的是为了代码的简洁,保证传入函数的单纯, 还有其他写法都可以
Lazyload
主函数
function Lazyload (Vue) {
return Class{
constructor (options){
this.options = options;
this.lazyImgPool = [];
}
bindLazy (el, bindings) {
Vue.nextTick(() => {
const scrollParent = getScrollParent(el);
if (scrollParent) {
scrollParent.addEventListener(
'scroll',
this.handleScroll.bind(this),
false
);
}
const lazyImg = new Lazyimg({
el,
src: bindings.value,
options: this.options,
imgRender: this.imgRender.bind(this)
});
this.lazyImgPool.push(lazyImg);
this.handleScroll()
})
}
}
其中的lazyImgPool 为保存每一个使用指令图片的数组,后期需要遍历判断
bindLazy 是指令的bind的回调函数,
el是指令绑定元素,bindings 是指令中一系列绑定的对象参数
例如:<div v-example:foo.bar="baz">
binding 参数会是一个这样的对象:
{
arg: 'foo',
modifiers: { bar: true },
value: /* `baz` 的值 */,
oldValue: /* 上一次更新时 `baz` 的值 */
}
使用Vue.nextTick是为了确保Dom渲染完成
通过函数getScrollParent找到可以滚动的元素scrollParent
给scrollParent绑定滚动事件handleScroll,这个地方一定要绑定this,否则handleScroll 中的this 会指向scrollParent,而不是当前实例,例子:
<!DOCTYPE html>
<html lang="en">
<body>
<div id="app" style="width:100px;height:100px;background:red"></div>
<script>
let app = document.getElementById("app")
class B {
constructor(){
this.name = 'zs'
app.addEventListener("click",this.fn)
}
fn(){
console.log(this)
}
}
let b = new B()
b()
</script>
</body>
</html>
如果不绑定 this 的话,打印结果:
使用bind绑定this
app.addEventListener("click",this.fn.bind(this)) 打印结果:
同时要把每一个使用指令的元素生成一个类Lazyimg实例,添加到lazyImgPool 中去
Lazyimg
为每一张使用指令的图片生成一个Lazyimg实例
class Lazyimg {
constructor({ el, src, options, imgRender }) {
this.el = el;
this.src = src;
this.options = options;
this.imgRender = imgRender;
this.loaded = false;
this.state = {
loading: false,
error: false
}
}
// 检测是否在可视范围内
// 不用检测 bottom,加载过的图片不会再执行
checkIsVisible () {
const { top } = this.el.getBoundingClientRect();
return top < window.innerHeight * (this.options.preload || 1.3);
}
// 加载图片,切换图片状态
loadImg () {
this.imgRender(this, 'loading');
imgLoad(this.src).then(() => {
this.state.loading = true;
this.imgRender(this, 'ok');
this.loaded = true;
}, () => {
this.state.error = true;
this.imgRender(this, 'error');
this.loaded = true;
})
}
}
Lazyimg实例接收参数el,src,options,imgRender
自身属性loaded 表示是否加载过
自身属性state 表示状态
有两个方法检测是否可视范围内checkIsVisable和加载图片loadImg, 其中重点是loadImg
这里面有个有个技巧,loadImg 使用了传入的方法imgRender,它由外部传入 ,把自身实例传入,可以由外部接收使用,达到由外部控制的效果,自己只做一个简单的传递使用功能
其中使用到了函数loadImg,来判断图片状态
loadImg
接收一个src,返回一个promise
function imgLoad (src) {
return new Promise((resolve, reject) => {
const oImg = new Image();
oImg.src = src;
oImg.onload = resolve;
oImg.onerror = reject;
})
}
自己生成一个实例
Image,通过实例方法onload/onerror, 来判断传入的src是否可用
handleScroll
判断绑定的元素是否在可视范围内,并且从未加载过
handleScroll () {
let isVisible = false;
this.lazyImgPool.forEach(lazyImg => {
if (!lazyImg.loaded) {
isVisible = lazyImg.checkIsVisible();
isVisible && lazyImg.loadImg();
}
})
}
这个时候,lazyImgPool 存放的是一个个的lazyImg 实例,有属性loaded,state,方法checkIsVisible,loadImg等
imgRender
交由Lazyimg使用的imgRender
const lazyImg = new Lazyimg({
xx,
xx,
...
imgRender: this.imgRender.bind(this)
});
在lazyImg中使用
loadImg(){
this.imgRender(this, 'loading');
imgLoad(this.src).then(() => {
...
this.imgRender(this, 'ok');
...
}, () => {
...
this.imgRender(this, 'error');
...
})
}
imgRender (lazyImg, state) {
const { el, options } = lazyImg;
const { loading, error } = options;
let src = '';
switch (state) {
case 'loading':
src = loading || '';
break;
case 'error':
src = error || '';
break;
default:
src = lazyImg.src;
break;
}
el.setAttribute('src', src);
}
总结
主要知识点
- 函数式编程控制参数个数
- 控制反转,
imgRender交由lazyImg统一控制 time:2022/11/1 18:52