在项目中使用常规的懒加载方式处理些资源,会在一些地方都需要添加懒加载处理(比如页面进入时候,滚动时候,部分资源异步加载等),同一个页面内重复触发懒加载,对于性能也会存在一部分的影响;还有部分手机机型有可能屏幕高度比较小,没必要一开始进入就懒加载,因为这个原因,寻找一个比较合理合适的解决方案处理。
1、常规懒加载
使用图片资源懒加载作为例子,一般都是在页面中进行资源监测,进入页面触发一次懒加载,滚动时候再绑定事件触发懒加载,这样页面就一开始进行了两次处理。如果页面有横向滚动的元素,就需要再一次绑定懒加载处理,有多个这种横向滚动等场景,就需要多次绑定处理。
- a、
dataset:设置自定义属性,方便通过element.dataset.keyname获取对应值 - b、
GetBoundingClientRect:提供了元素的大小及其相对于视口的位置
function throttle(fn, wait) {
let inThrottle, lastFn, lastTime;
return function () {
const context = this,
args = arguments;
if (!inThrottle) {
fn.apply(context, args);
lastTime = Date.now();
inThrottle = true;
} else {
clearTimeout(lastFn);
lastFn = setTimeout(function () {
if (Date.now() - lastTime >= wait) {
fn.apply(context, args);
lastTime = Date.now();
}
}, Math.max(wait - (Date.now() - lastTime), 0));
}
};
}
const lazyLoadImgObj = {
isIn(el) {
var bound = el.getBoundingClientRect();
var clientHeight = window.innerHeight;
return el.getAttribute("lazy") === "loading" && bound.top <= clientHeight;
},
checkImg() {
let imgs = document.querySelectorAll("img[lazy='loading']");
Array.from(imgs).forEach((el) => {
if (lazyLoadImgObj.isIn(el)) {
lazyLoadImgObj.loadImg(el);
}
});
},
loadImg(el) {
if (el.src !== el.dataset.src) {
el.src = el.dataset.src;
el.setAttribute("lazy", "loaded");
}
},
};
const checkImgFunc = throttle(lazyLoadImgObj.checkImg, 500);
document.addEventListener('DOMContentLoaded', checkImgFunc);
window.addEventListener('scroll', checkImgFunc);
<body style="display: flex;flex-direction: column;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
</body>
2、使用IntersectionObserver处理
- 含义:web api 提供了一种
异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法 主要是用来监听可见区域的变化 - root参数配置:表示需要与元素交互的跟元素,默认null表示视口
- rootMargin参数配置:表示根元素需要外拓的范围
- thresholds参数配置:表示元素进入监听区域内的所占比列,默认为0,表示两个刚好碰到,才会触发回调。为1,表示完全进入,才触发回调
- 兼容性:
<script>
// 懒加载函数
function lazyLoadImages() {
// 选择所有需要懒加载的图片元素
const images = document.querySelectorAll("img[lazy='loading']");
// 检查图片是否在视口内的函数
const isInViewport = (image) => {
const rect = image.getBoundingClientRect();
return (
rect.top <= window.innerHeight &&
rect.left <= window.innerWidth &&
rect.bottom >= 0 &&
rect.right >= 0
);
};
// 加载图片的函数
const loadImage = (image) => {
if (image.dataset.src) {
image.src = image.dataset.src;
image.setAttribute("lazy", "loaded");
image.removeAttribute('data-src');
}
};
// IntersectionObserver 回调函数
const onIntersection = (entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadImage(entry.target);
observer.unobserve(entry.target);//加载成功后,移除监听对象
}
});
};
// 创建 IntersectionObserver 实例
const observer = new IntersectionObserver(onIntersection, {
root: null,
rootMargin: '0px',
threshold: 0,
});
// 观察每个图片元素
images.forEach((image) => {
if (isInViewport(image)) {
loadImage(image);
} else {
observer.observe(image);
}
});
}
// 在 DOMContentLoaded 事件触发时调用懒加载函数
document.addEventListener('DOMContentLoaded', lazyLoadImages);
</script>
<body style="display: flex;flex-direction: column;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
<img lazy='loading' src="https://www.minigame.vip/static/images/lazy-img.png" data-src="https://res.minigame.vip/gc-assets/bubble-shooter/bubble-shooter_banner.webp" style="height: 300px;width: 600px;">
</body>
3、结果
两种方式的结果是一样的
- 1、旧的方式需要多次绑定事件触发,且会存在部分元素重复绑定事件,没有兼容性问题。
- 2、使用
IntersectionObserver触发的方式比较统一,且只要设置一次,对于在首屏资源中加载,优势也比较大,可以等其他重要资源加载好之后,再启动该方法执行全局图片资源懒加载。既可以保证速度也可以保证效果。缺点是部分老旧的浏览器会存在兼容性问题,可以使用polyfill处理