前言

平时网页中会有很多图片,图片有一个加载过程,很多情况,我们是不需要一开始直接加载的,当浏览器的视图滚动到图片时,开始加载可以节省网络资源,这就是我们常说的懒加载,当然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