图片懒加载
# 为什么图片需要懒加载
- 图片数量和体积过大往往会影响页面加载,容易造成不良的用户体验
- 实现懒加载后,可以减少浏览器和服务器压力
推荐:Medium (opens new window) 非常流畅的图片懒加载应用网站,使用了 aspect-ratio
创建占位元素,并且添加了 blur
效果
# 原理
在合适的时机替换 src
地址
<div>
<img src="占位图片" data-src="真正的图片地址" alt="">
<img src="占位图片" data-src="真正的图片地址" alt="">
<img src="占位图片" data-src="真正的图片地址" alt="">
many img...
</div>
1
2
3
4
5
6
2
3
4
5
6
# 方案一:原生懒加载
判断浏览器是否支持原生的懒加载
浏览器不支持的话,可以使用这个库:lazysizes (opens new window)
if ("loading" in HTMLImageElement.prototype) {
console.log("Yes!");
// <img src="xxx" alt="" loading="lazy">
} else {
console.log("No~");
// 引入 lazysizes
// 或者自定义逻辑
}
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 方案二:监听页面滚动
监听页码滚动,判断图片进入视窗后加载图片
对需要懒加载的图片添加标记,加载后移除
const imgs = document.getElementsByTagName("img");
const viewHeight = window.innerHeight || document.documentElement.clientHeight;
let imgIndex = 0;
const canView = (element) => {
return element.getBoundingClientRect().top <= viewHeight;
};
const main = () => {
for (let i = imgIndex; i < imgs.length; i++) {
const element = imgs[i];
if (canView(element)) {
element.src = element.dataset.src;
imgIndex = i + 1;
}
}
};
window.addEventListener("scroll", main);
window.onload = main;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 方案三:IntersectionObserver
const imgs = document.querySelectorAll("img[data-src]");
const loadImg = (image) => {
image.setAttribute("src", image.getAttribute("data-src"));
image.addEventListener("load", () => {
image.removeAttribute("data-src");
});
};
const observerImg = new IntersectionObserver((items, observer) => {
items.forEach((item) => {
if (item.isIntersecting) {
loadImg(item.target);
observer.unobserve(item.target);
}
});
});
imgs.forEach((img) => {
observerImg.observe(img);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 滚动性能优化
利用 requestAnimationFrame
保证每一帧只执行一次滚动事件
window.addEventListener("scroll", handleScroll);
handleScroll();
function handleScroll() {
beforeNextFrameOnce(_handleScroll);
}
function _handleScroll() {
// doing somthing
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
beforeNextFrameOnce()
:
let onceCbs = [];
const paramsMap = new WeakMap();
function flushOnceCallbacks() {
onceCbs.forEach((cb) => cb(...paramsMap.get(cb)));
onceCbs = [];
}
function beforeNextFrameOnce(cb, ...params) {
paramsMap.set(cb, params);
if (onceCbs.includes(cb)) return;
onceCbs.push(cb) === 1 && requestAnimationFrame(flushOnceCallbacks);
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11