vue-lazyload实现
vue面试常被问到
vue-lazyload
实现原理,其实考察的是我们对vue指令的掌握
使用
先来使用一下这个
vue-lazyload
插件
插件引入
npm install vue-lazyload
使用
<template>
<ul class="box">
<li v-for="(img, index) in list" :key="index">
<img v-lazy="img" />
</li>
</ul>
</template>
<script>
import axios from "axios";
import LazyLoad from "vue-lazyload";
import Vue from "vue";
import logo from "@/assets/logo.png";
Vue.use(LazyLoad, {
loading: logo,
preload: 1.2, //加载距离屏幕1.2倍处图片
});
export default {
name: "LazyLoadTest",
data() {
return { list: [] };
},
// 请求放到created还是mounted?,服务端渲染支持created 所以希望大家把请求写在这里保持和服务端一致
async mounted() {
const { data: imgs } = await axios.get("http://localhost:4000/api/list");
this.list = imgs;
console.log(this.list);
},
};
</script>
<style>
.box {
width: 400px;
height: 400px;
overflow: scroll;
}
img {
width: 100px;
height: 150px;
}
</style>
实现vue-lazyload
import _ from 'lodash';
const VueLazyLoad = {
//vue插件实现,Vue.use(插件名,options),vue内容会调用install,并将Vue和options传入,这样能保证Vue的版本
install(Vue, options) {
//获取LazyLoad类
const LazyLoad = lazy(Vue);
//获取实例
const instance = new LazyLoad(options)
//指令
Vue.directive('lazy', {
bind: instance.bind.bind(instance), //这里注意需要改变this指向
unbind: instance.unbind.bind(instance)
})
}
}
function lazy(Vue) {
//存储每个图片的属性
class ReactiveListener {
constructor({
el,
src,
options
}) {
this.el = el; //图片元素
this.src = src; //图片地址
this.options = options; //用户配置的参数
this.state = {
loading: false
}; //图片是否已经加载
}
//判断是否在可视区
checkInView() {
const {
top
} = this.el.getBoundingClientRect();
return top < window.innerHeight * this.options.preload;
}
//加载图片
load() {
//渲染loading图
render(this, 'loading');
//加载真实图片
loadImg(this.src, () => {
//加载真实图片成功回调,渲染真实图片
render(this, 'success')
}, () => {
//加载真实图片失败的回调,渲染失败图
render(this, 'error')
})
}
}
return class LazyLoad {
constructor(options) {
this.options = options;
this.listeners = []; //存储所有图片元素
this.bindHandler = false; //是有已经绑定过
}
//绑定
bind(el, bindings) {
Vue.nextTick(() => {
//图片地址
const src = bindings.value;
const listener = new ReactiveListener({
el,
src,
options: this.options
});
//存储每一个图片元素
this.listeners.push(listener);
//获取滚动的父元素
const pElm = parentScoll(el);
//绑定事件
if (!this.bindHandler) {
//这里使用lodash节流
const lazyHandler = _.throttle(this.lazyLoadHandler.bind(this));
pElm.addEventListener('scroll', lazyHandler, {
passive: true //不阻止其他事件
});
this.bindHandler = true; //保证滚动事件注册一次
}
//初始化,当前要展示的
this.lazyLoadHandler();
})
}
//解绑
unbind() {
}
//
lazyLoadHandler() {
this.listeners.forEach(listener => {
//如果没有加载过,并且在加载区域
//已经加载过的
if (listener.state.loading) return;
listener.checkInView() && listener.load()
})
}
}
}
//获取滚动的父元素
function parentScoll(el) {
let parent = el.parentNode;
while (parent) {
if (/scroll/.test(getComputedStyle(parent)['overflow'])) {
return parent;
}
parent = parent.parentNode;
}
return parent;
}
//加载真实图片
function loadImg(src, resolve, reject) {
const img = new Image();
img.src = src;
img.onload = resolve;
img.onerror = reject;
}
//渲染图片,真实图,加载图,错误图
function render(listener, state) {
let src = '';
switch (state) {
case 'loading': //显示加载图
src = listener.options.loading;
break;
case 'success': //显示真实图
src = listener.src;
break;
case 'loading': //显示错误图
src = listener.options.error;
break;
}
listener.el.setAttribute('src', src);
}
export default VueLazyLoad;