原理
-
首先,不给懒加载的
标签设置‘src’属性,并且设置自定义属性‘data-src’,值为图片路径,并且设置class=“lazy-load”
-
然后,等页面加载完成,获取class为‘lazy-load’的
元素集合并且遍历,看哪个
元素在可视区域内,将在可视区域内的
元素的src属性设置为‘data-src’的值,并且删除该元素class属性上的‘lazy-load’类(从集合删除)
实现方式
方式1 - getBoundingClientRect
let dom = document.getElementById('id'),
rect = dom.getBoundingClientRect();
const {top, bottom, left, right} = rect;
根据el.getBoundingClientRect()
获取元素的大小及其相对于视口的位置信息
根据document.documentElement.clientHeight
获取可视视口的高度
监听scroll
事件,每一次滚动都去遍历集合中的img元素,判断其是否在视口的可视范围内
并使用节流函数(throttle)
优化滚动事件的性能
// 懒加载函数
function lazyLoad() {
// 浏览器可视范围高度
let clientH = document.documentElement.clientHeight;
// 根据标识,获取懒加载img元素集合
let imgs = document.querySelectorAll('.lazy-load');
imgs.forEach(img => {
// top:图片顶部到视口顶部距离;bottom:图片底部到视口顶部距离
let {top, bottom} = img.getBoundingClientRect();
// 判断img是否在可视范围内
if(top <= clientH && bottom >= 0) {
img.src = img.dataset.src;
img.classList.remove('lazy-load');
}
});
}
// 节流函数返回的函数
let throttled = throttle(lazyLoad);
// 监听滚动事件
window.addEventListener('scroll', throttled);
缺点: 需要监听scroll事件,scroll事件是伴随着大量计算,虽然我们可以通过节流函数来提高性能,但还是会有性能浪费的问题。
方式2 - IntersectionObserver
根据IntersectionObserver构造函数创建观察者,观察元素是否在可视范围内。
function lazyLoad() {
// 创建观察者
let iObserver = new IntersectionObserver((entries, observer) => {
/*
* 观察元素在可视范围内的回调
* */
entries.forEach(enter => {
// 如过在可视范围内
if (enter.isIntersecting) {
// 获取观察的img元素
let img = enter.target;
img.src = img.dataset.src;
img.classList.remove('lazy-load');
// 停止观察
iObserver.unobserve(img);
}
});
});
// 根据标识,获取懒加载img元素集合
let imgs = document.querySelectorAll('.lazy-load');
imgs.forEach(img => {
// 将img元素添加观察
iObserver.observe(img);
});
}
lazyLoad();
缺点: IntersectionObserver的兼容性差。
最终方案 - 方式1 + 方式2
直接上代码
class LazyLoad {
constructor(el) {
// 懒加载img元素集合
this.imgs = document.querySelectorAll(el);
this.init();
}
// 初始化
init() {
// 是否兼容 IntersectionObserver
if ('IntersectionObserver' in window) {
this.lazyLoadByIntersectionObserver();
} else {
this.lazyLoadByScroll();
}
}
// 通过IntersectionObserver实现的懒加载
lazyLoadByIntersectionObserver() {
// 创建观察者
let iObserver = new IntersectionObserver((entries, observer) => {
/*
* 观察元素在可视范围内的回调
* */
entries.forEach(enter => {
// 如过在可视范围内
if (enter.isIntersecting) {
// 获取观察的img元素
let img = enter.target;
img.src = img.dataset.src;
img.classList.remove('lazy-load');
// 停止观察
iObserver.unobserve(img);
}
});
});
this.imgs.forEach(img => {
// 将img元素添加观察
iObserver.observe(img);
});
}
// 滚动事件的懒加载
lazyLoadByScroll() {
// 节流函数返回的函数
let throttled = throttle(this.scrollFn);
// 监听滚动事件
window.addEventListener('scroll', throttled.bind(this));
}
scrollFn() {
// 浏览器可视范围高度
let clientH = document.documentElement.clientHeight;
this.imgs.forEach(img => {
// top:图片顶部到视口顶部距离;bottom:图片底部到视口顶部距离
let {top, bottom} = img.getBoundingClientRect();
// 判断img是否在可视范围内
if (top <= clientH && bottom >= 0) {
img.src = img.dataset.src;
img.classList.remove('lazy-load');
}
});
}
}
new LazyLoad('.lazy-load');