/*
vue插件一枚
1.长列表中图片是否加载完,没有加载完显示一张默认的加载图片
2.100张图片 默认优先加载可视区域(1屏幕2屏幕)的图片,不显示的不加载,
3. 滚动的时候图片元素是否在规定的加载区域没,如果在就加载
* 添加滚动的监听
* 谁有overflow scroll auto
4. new Image() onload()
5. 对外抛出的不是一个类 外部没有实例化
*/
//创建了一个检测父元素有没有scroll的样式,要一层一层找
function getScrollParent(el) {
// 获取子元素绑定overflow: scroll, auto 属性的父元素
//console.log(el); 这里的el是img元素
let parent = el.parentNode;
while (parent) {
if (/(scroll)|(auto)/.test(getComputedStyle(parent)["overflow"])) {
return parent;
}
parent = parent.parentNode;//要每次都要重新给parent赋值,需要一层层向上找,有的话就把这个父元素的标签返回出来,没有就返回null
}
return null;
}
// 同步加载图片方法,在函数中加载图片的时候使用
function asyncImgLoad(src, resove, reject) {
let img = new Image();
img.src = src;
img.onload = resove;
img.error = reject;
}
// 对逻辑功能进行拆分
const MyLazy = (Vue) => {
// 为了实现元素和对象的关联 硬生生的创建一个绑定对象
class ReactiveListene { //下面在实例化listener的时候用,把这些参数传进来
constructor({ el, src, renderEl, preload }) {
this.el = el;
this.src = src;
this.renderEl = renderEl;
this.preload = preload
this.state = { loading: false }; // 还没加载
}
// 检测是不是在可视区域的范围内,如果在就需要检测
checkInView() {
let { top } = this.el.getBoundingClientRect();
//console.log(window.innerHeight,top);
return top < window.innerHeight * this.preload;
}
// 加载图片的方法
load() {
// 优先加载loading
// console.log(this);//这里的this也是每一个listener
this.renderEl(this, "loading");
// 加载正常图片,就是执行外部的asyncImgLoad函数
asyncImgLoad(
this.src,
() => {
// 图片加载成功了后 渲染成功图片,这里是resolve函数
this.state.loading = true;
this.renderEl(this, "success");
},
() => {
this.state.loading = true;
// 图片加载失败了后 渲染失败图片
this.renderEl(this, "error");
}
);
}
}
return class LazyClass { //返回一个类,里面的的options是在main.js里面在进行vue.user的时候传递的参数,包括loading,preload等参数
constructor(options) {
const {preload, loading} = options;//把参数进行解构
this.preload = preload || 1;
this.loading = loading;
this.bindScrollEvent = false;
// 保存所有的元素
this.listenerArr = [];//这里保存了所有的listener,在下面实例化的listener
}
handleScroll() {
// 滚动的监听事件
this.listenerArr.map((listener) => { //对之前存的每一个listener进行触发加载loadshi事件,只有满足isInView的时候才会加载,也就是不在可视化里面的图片不进行加载,只有滚动到了可视化的才开始加载图片
// 图片是否已加载 -> 是不是在可视区内需要加载
if (!listener.state.loading) {
let isInView = listener.checkInView();
isInView && listener.load();
}
return listener;
});
}
add(el, bindings) {
// 1.添加滚动监听 2.将与图片对应的listener对象存起来
//console.log("要绑定的元素", el, bindings, this);
Vue.nextTick(() => {
const scrollBox = getScrollParent(el);//找到绑定scroll的父元素进行绑定滚动事件
if (scrollBox && !this.bindScrollEvent) {
console.log("xxxxxxxxxx");
this.bindScrollEvent = true;
scrollBox.addEventListener("scroll", this.handleScroll.bind(this));
}
// 在绑定元素的时候填添加检测对象
const listener = new ReactiveListene({ //在执行完这个函数的时候,就会实例化了listener这个对象了,因为在用的时候通过v-for,所以执行了多次这个函数,也就创建了多个listener,所以把每个listener添加到listenerArr数组中
el,
src: bindings.value,//这里的bindings里的value就是自定义组件里面=后面的值,在这里就是图片的路径
renderEl: this.renderEl.bind(this),
preload: this.preload
});
this.listenerArr.push(listener);
// 初始化触发
this.handleScroll();
});
}
// 渲染方法
renderEl(listener, state) {
let el = listener.el;
let src = "";
switch (state) {
case "loading":
src = this.loading
break;
case "error":
src = "";
break;
case 'success':
src = listener.src;
break;
}
el.setAttribute("src", src);
}
};
};
// 注册自定义指令
const MyLazyLoad = {
install(Vue, options) {
console.log(Vue, options);
// 创建自定义指令
const LazyClass = MyLazy(Vue);
const lazy = new LazyClass(options);
Vue.directive("hehe", {
bind: lazy.add.bind(lazy), // 希望我们add方法的时候 this一直指向的是实例
});
},
};
export default MyLazyLoad;
思路:
刚开始通过install方法进行挂载一个插件,也就是我们自己封装的v-lazy,有两个参数,一个vue:是vue的构造器,一个options:是在vue.use的时候传入的值,在自定义参数的时候用的bind方法,用到里面前两个参数,一个是el:用指令的dom,一个是building参数:里面还好几个参数name:插件的名字,value,用插件里面的值,在用v-lazy='11111',就是111111
//接下来就是我们在bind函数的操作了,首先我们在全局封装了两个方法,一个是getScrollParent用来查找有滚动事件的父元素,参数是el,也就是传入的绑定我们自定义指令的dom,一个是asyncImgLoad,用来图片的加载三个参数,一个是图片的地址,一个是加载成功后的函数,另一个是加载失败的函数
//我们又创建了一个函数MyLazy,返回值是一个LazyClass类,接下来就是这个函数的逻辑
//我们把vue当作参数穿进去,首先创建一个类ReactiveListene,这也就是相当于每一个绑定之类的dom,我们把他分装成一个类,有助于传值,定义他的方法,在constructor里面有参数el:dom,src:图片地址,renderEl:渲染方法,有个重要的state:{load:false},后续他通过这个判断加不加载,另外还有两个方法checkInView:判断元素是否在可视范围内,一个图片加载的方法load,在里面里面就进用了我们在全局写的图片加载方法,所以只有调用load方法图片才会加载不掉用的话就不加载//注意我们想图片一开始先加载一个我们预先设定的图片,所以在load函数调用renderEl方法的时候我们第二各参数传递“loading”,在renderEl函数进行swich判断的时候,先加载我们预定的图片,之后在执行我们全局asyncImgLoad加载图片的函数,在,并把state里面的loading改为true,在以后判断是true已经加载过得就不用加载了,在调用renderEl方法,把图片修改成真正的图片地址
//我们又封装了一个类,也就是return的LazyClass类,在里面我们把options传进去,获取到app.use里面传的的,例如loading,图片首次加载的地址,然后定义了一个add方法,在里面进行了给具有滚动事件的父元素绑定滚动事件,并把所有绑定v-lazy自定义事件的元素,添加到数组listenerArr中,在滚动事件中进行对
//listenerArr进行遍历,判断哪一个元素在可视化里面,并且没有加载过(loading是flase),就执行load方法