前言
平时网页中会有很多图片,图片有一个加载过程,很多情况,我们是不需要一开始直接加载的,当浏览器的视图滚动到图片时,开始加载可以节省网络资源,这就是我们常说的懒加载,当然CSR下的组件配合React.lazy的动态导入也是可以进行懒加载的。
思路
使用IntersectionObserverAPI就可以实现懒加载的效果 我们可以用IntersectionObserver去监听需要懒加载的元素,当该元素滚动到视图区域时,请求该资源。
首先创建一个MyLazyLoad.tsx文件
1import {
2 CSSProperties,
3 FC,
4 ReactNode,
5 useRef,
6 useState
7} from 'react';
8
9interface MyLazyloadProps{
10 className?: string,
11 style?: CSSProperties,
12 placeholder?: ReactNode,
13 offset?: string | number,
14 width?: number | string,
15 height?: string | number,
16 onContentVisible?: () => void,
17 children: ReactNode,
18}
19
20const MyLazyload: FC<MyLazyloadProps> = (props) => {
21
22 const {
23 className = '',
24 style,
25 offset = 0,
26 width,
27 onContentVisible,
28 placeholder,
29 height,
30 children
31 } = props;
32
33 const containerRef = useRef<HTMLDivElement>(null);
34 const [visible, setVisible] = useState(false);
35
36 const styles = { height, width, ...style };
37
38 return <div ref={containerRef} className={className} style={styles}>
39 {visible? children : placeholder}
40 </div>
41}
42
43export default MyLazyload;
44
我们通过接收children元素来绑定lazy的效果,Props接收类名,样式,偏移量(当元素出现在视图的高度为多少像素时加载),宽度,高度,元素加载时的回调函数,占位元素,子元素(绑定lazy的元素)。
接下来我们new出 InterSectionObserve 用Ref去保存
1const elementObserver = useRef<IntersectionObserver>();
2
3useEffect(() => {
4 const options = {
5 rootMargin: typeof offset === 'number' ? `${offset}px` : offset || '0px',
6 threshold: 0
7 };
8
9 elementObserver.current = new IntersectionObserver(lazyLoadHandler, options);
10
11 const node = containerRef.current;
12
13 if (node instanceof HTMLElement) {
14 elementObserver.current.observe(node);
15 }
16 return () => {
17 if (node && node instanceof HTMLElement) {
18 elementObserver.current?.unobserve(node);
19 }
20 }
21}, []);
22
这里的 rootMargin 就是距离多少进入可视区域就触发,和参数的 offset 一个含义。
threshold 是元素进入可视区域多少比例的时候触发,0 就是刚进入可视区域就触发。
然后用 IntersectionObserver 监听 div。
之后定义下 lazyloadHandler:
1function lazyLoadHandler (entries: IntersectionObserverEntry[]) {
2 const [entry] = entries;
3 const { isIntersecting } = entry;
4
5 if (isIntersecting) {
6 setVisible(true);
7 onContentVisible?.();
8
9 const node = containerRef.current;
10 if (node && node instanceof HTMLElement) {
11 elementObserver.current?.unobserve(node);
12 }
13 }
14};
当 isIntersecting 为 true 的时候,就是从不相交到相交,反之,是从相交到不相交。
这里设置 visible 为 true,回调 onContentVisible,然后去掉监听。
最终代码
1import {
2 CSSProperties,
3 FC,
4 ReactNode,
5 useRef,
6 useState
7} from 'react';
8
9interface MyLazyloadProps{
10 className?: string,
11 style?: CSSProperties,
12 placeholder?: ReactNode,
13 offset?: string | number,
14 width?: number | string,
15 height?: string | number,
16 onContentVisible?: () => void,
17 children: ReactNode,
18}
19
20const MyLazyload: FC<MyLazyloadProps> = (props) => {
21
22 const {
23 className = '',
24 style,
25 offset = 0,
26 width,
27 onContentVisible,
28 placeholder,
29 height,
30 children
31 } = props;
32
33 const containerRef = useRef<HTMLDivElement>(null);
34 const [visible, setVisible] = useState(false);
35
36 const styles = { height, width, ...style };
37
38 const elementObserver = useRef<IntersectionObserver>();
39
40 function lazyLoadHandler (entries: IntersectionObserverEntry[]) {
41 const [entry] = entries;
42 const { isIntersecting } = entry;
43
44 if (isIntersecting) {
45 setVisible(true);
46 onContentVisible?.();
47
48 const node = containerRef.current;
49 if (node && node instanceof HTMLElement) {
50 elementObserver.current?.unobserve(node);
51 }
52 }
53 };
54
55
56useEffect(() => {
57 const options = {
58 rootMargin: typeof offset === 'number' ? `${offset}px` : offset || '0px',
59 threshold: 0
60 };
61
62 elementObserver.current = new IntersectionObserver(lazyLoadHandler, options);
63
64 const node = containerRef.current;
65
66 if (node instanceof HTMLElement) {
67 elementObserver.current.observe(node);
68 }
69 return () => {
70 if (node && node instanceof HTMLElement) {
71 elementObserver.current?.unobserve(node);
72 }
73 }
74}, []);
75
76
77 return <div ref={containerRef} className={className} style={styles}>
78 {visible? children : placeholder}
79 </div>
80}
81
82export default MyLazyload;
83
个人笔记记录 2021 ~ 2025