更简单的图片懒加载实现

前言

开发过程中,发现了一个更方便来做图片懒加载的方法,简单记录一下

正文

之前比较常见的对图片进行懒加载的方法,一般都是手动获取到 img dom,然后通过监听 scroll 事件等,来判断 img dom 是否出现在用户视窗(或目标范围),然后在设置真实 url 来加载图片;拆分这个实现过程,中间做了两件事:

1. 监听img dom的位置
2. 设置img的真实url 开始加载图片

对于第一步需要来写大量的判断代码,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 获取需要监听的DOM元素
var targetElement = document.querySelector("#target");

// 判断元素是否在视窗范围内的函数
function isElementInViewport(el) {
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <=
(window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}

// 处理滚动事件
function handleScroll() {
if (isElementInViewport(targetElement)) {
console.log("目标元素进入视窗");
// 在元素进入视窗时执行你想要的操作
} else {
console.log("目标元素离开视窗");
// 在元素离开视窗时执行你想要的操作
}
}

// 添加滚动事件监听器
window.addEventListener("scroll", handleScroll);

// 初始化页面时检查一次元素是否在视窗范围内
handleScroll();

其次,另一点就是可能项目里对全局挂载事件可能不只有这一处,当挂载到全局的事件很多且没有手动移除,可能带来性能损耗(一直会对其传入的回调分配内存等)

新的实现方式 ,就是通过 Intersection Observer API ,来实现我们的第一步的操作,除过兼容性可能需要关注,基本可以完美替代上述的方案

他的原理也很简单,通过指定一个我们需要监听的 dom,然后显示传入一个回调函数,当这个 dom 出现在视窗(或指定的某个 dom)内时,会调用传入的回调

简单写个例子,这个例子是当我们点击 show 按钮的时候,id 为 panel 的 div 会展示出来,接着就会触发我们的回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
div {
width: 500px;
height: 500px;
background-color: rebeccapurple;
display: none;
}
</style>
</head>
<body>
<div id="panel"></div>
<button id="show">show</button>
<script>
const show = document.querySelector("#show");
const panel = document.querySelector("#panel");
const func = () => {
// 。。。业务逻辑
};
const panelObserber = new IntersectionObserver(
(arg) => {
console.log("debug callback", arg);
if (arg[0].isIntersecting) {
func();
}
},
{
root: document.querySelector("body"),
threshold: 1,
}
);
panelObserber.observe(panel);
show.addEventListener("click", () => {
panel.style.display = "block";
});
</script>
</body>
</html>

封装一个 react Hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { useState, useRef, useEffect } from "react";

const useLazyLoad = (imageSrc: string, placeholderSrc: string) => {
const [imageSrcState, setImageSrc] = useState(placeholderSrc);
const imageRef = useRef(null);

useEffect(() => {
// 创建IntersectionObserver实例并配置回调函数
const observer = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
// 当图片进入可视区域时,替换图片源
setImageSrc(imageSrc);
// 停止观察
observer.unobserve(imageRef.current!);
}
});

// 观察ref所指向的元素
if (imageRef.current) {
observer.observe(imageRef.current);
}

// 清理函数:组件卸载时取消观察
return () => observer.disconnect();
}, [imageSrc, placeholderSrc]);

return [imageSrcState, imageRef];
};

export default useLazyLoad;

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!