实现这种图片懒加载方法其实有多种方式
- 将每个
img
元素的top
值与屏幕的高度作比较,在的话,就替换成要加载的图片。 - 直接调用
window
上的IntersectionObserver
等API
,目前大多数浏览器已支持。可以在回调中直接调用API
就可以知道当前这张图片在不在屏幕中。
第一种方法涉及到的知识点
- 自定义属性
- 节流
- 图片在屏幕的top值与屏幕高度作比较
第二种方法涉及到的知识点
主要是熟悉window
上的几个API
IntersectionObserver
,为构造函数IntersectionObserverEntry
,为构造函数intersectionRatio
:IntersectionObserverEntry.prototype
上的属性
第一种方法整体思路
- 搞几张图片,src属性和data-src属性的值提前写好,如果是接口返回的,在返回成功后赋值即可。
- 对每一张图片都要去做判断,图片在屏幕的top值是否< 屏幕高度。
- 这里需要优化一下,因为有的图片加载完成了之后,就不用去判断了,所以可以重新获取一下这个imgList列表做循环。
- imgList.length === 0时,移除监听。
<template>
<div>
<div class="container" v-for="(item, i) in imgsList" :key="i">
<img class="lazy" :src="item.default" :data-src="item.showPath" />
</div>
</div>
</template>
<script>
export default {
data() {
return {
flag: false,
imgArr: [],
};
},
computed: {
// 提前把数据写死
imgsList() {
let arr = [],
brr = new Array(7);
brr.fill(0);
arr = brr.map((e, i) => {
return {
default: require(`./imgs/8.webp`),
showPath: require(`./imgs/${i + 1}.webp`),
};
});
return arr;
},
},
created() {
this.showImgs(); // 默认去加载屏幕中的图片
window.addEventListener("scroll", _.throttle(this.showImgs,200)); // 滚动的时候也去加载
},
mounted() {
// 获取需要懒加载的图片
this.imgArr = document.getElementsByClassName("lazy");
// 类数组转换为数组
this.imgArr = [].slice.call(this.imgArr, 0);
// console.log(this.imgArr.classList);
},
methods: {
showImgs() {
this.imgArr.forEach((e) => {
// -300是为了好观察效果
if (e.getBoundingClientRect().top <= window.innerHeight - 300) {
// console.log(e.classList);
// classList是img元素上的class属性的集合,是一个数组,采用remove方法去移除相关类名。
// 把找到的img元素上的lazy类名移除。
e.classList.remove("lazy");
// 自定义属性赋值
e.src = e.dataset.src;
/* console.log(this.imgArr,e);// 第一个img是去除掉的,e=>去除掉class的e
*/
this.imgArr = this.imgArr.filter((el) => el !== e);
if (this.imgArr.length === 0) {
// 加载完成后,移除scroll事件
window.removeEventListener("scroll", this.showImgs);
}
}
});
// this.imgArr.forEach((e)=>{
// })
},
},
};
</script>
<style lang='stylus' scoped>
.container {
height: 300px;
img {
height: 100%;
}
}
</style>
有一个地方需要注意:为什么可以拿到未删除.lazy属性的集合?
this.imgArr = this.imgArr.filter((el) => el !== e);
可以看下面这个例子:
<div><img src="" alt="" class="lazy"></div>
<div><img src="" alt="" class="lazy"></div>
<div><img src="" alt="" class="lazy"></div>
<div><img src="" alt="" class="lazy"></div>
<div><img src="" alt="" class="lazy"></div>
let imgs = document.getElementsByClassName('lazy')
imgs = [].slice.call(imgs,0) // imgs存的是DOM元素集合[<img/>,<img/>,...]
imgs.forEach((e,i)=>{
if(i === 0){
e.classList.remove('lazy')
console.log(e)
let brr = imgs.filter((el)=> {
console.log('e',e)
console.log('el',el)
el !== e
// 为什么两者可以相互比较,因为存储的是DOM元素,不是对象。
})
}
})
已删除的,在原数组中通过比较过滤出不一样的。
节流和防抖
在触发滚动事件中,可以使用节流,在200ms就去执行一次showImgs
()方法。
下面来说一下节流和防抖的原理实现。
节流:在xx时间段内执行一次。如果你在这个时间段内去触发事件,则直接return.可以实现就是,每隔多长时间需要去干嘛。
防抖:在xx时间段内执行一次。但是如果你在这个时间段内去触发事件,则直接从头开始。比如说一个按钮,如果在5s内如果你一会点击,则最后一次点击之后的5s之后会执行,之前的都不算。可以实现一些需求,比如,一直滚动,等你不滚动的时候,去干些啥。
<button id="btn">点击</button>
let btn = document.getElementById('btn')
// btn.addEventListener('click', throttle(fn, 2000, 20, 30))
btn.addEventListener('click', debounce(fn, 2000, 20, 30))
节流:
function throttle(fun, time, ...args) {
let timer = null
return function proxy() {
if (timer) {
// timer有值直接return
return
}
// timer: timeoutID,是一个正整数,表示定时器的编号
timer = setTimeout(() => {
fun(...args)
timer = null //执行完之后就可以继续点击了
}, time)
}
}
防抖:
function debounce(fun,time,...args){
let timer = null
return function proxy(){
if(timer){
// 如果有定时器则清空掉,重新计时
clearTimeout(timer)
}
timer = setTimeout(()=>{
fun(...args)
timer = null
},time)
}
}
第二种实现方式代码
methods:{
lazyLoad() {
console.time() //计算代码运行时间
// 判断浏览器兼容性
if (
"IntersectionObserver" in window &&
"IntersectionObserverEntry" in window &&
"intersectionRatio" in window.IntersectionObserverEntry.prototype
) {
// 创建IntersectionObserver实例,并传递匿名回调函数
let lazyImageLoader = new IntersectionObserver(function (
entries,
observer
) {
// console.log(entries); // 每一张图片的位置信息
entries.forEach((e) => {
// 判断图片是否在视窗中
if (e.isIntersecting) {
// console.log('yes')
let lazyImage = e.target;
lazyImage.src = lazyImage.dataset.src;
// 加载后清除类名,并且取消监控
lazyImage.classList.remove("lazy");
lazyImageLoader.unobserve(lazyImage);
}
});
});
console.timeEnd();// 0.04736328125 ms
this.imgArr.forEach((e) => {
// console.log(lazyImageLoader)
lazyImageLoader.(e); //没有返回值,给每一张图片增加监控
/* 目前理解就是IntersectionObserver.prototype.observe方法可以执行
new IntersectionObserver(function(){...})的回调 */
});
}
},
}
在项目中判断在视口才去发请求
// 根据id来获取DOM,在调取observe()方法的时候,传的参数要是一个element,也就是一个DOM元素。
checkGetQues: _.throttle(function (ele, callback) {
{
if ("IntersectionObserver" in window &&
"IntersectionObserverEntry" in window &&
"intersectionRatio" in window.IntersectionObserverEntry.prototype) {
const lazyImageLoader = new IntersectionObserver((entries, observer) => {
// console.log('entries', entries)
if (entries[0].isIntersecting) {
callback();
// 解除监测
lazyImageLoader.unobserve(ele)
}
}, {
rootMargin: "100px"
})
lazyImageLoader.observe(ele)
}
}
}, 1000),
注意点:只要创建了这个对象new IntersectionObserver()
,调用了observe()
方法,window
对象会自动去检测,不用你自己去写滚动方法.
最后
在真实项目中,可以借用第三方库lodash
来实现节流和防抖需求。
btn.onclick = _.throttle(fn, 1000);
btn.onclick = _.debounce(fn, 1000);
在vue中使用节流和防抖:
handleScroll: _.throttle(function () {
// dosomething
}, 200),
},