手写图片懒加载-------IntersectionObserver------JavaScript和React

实现思路:

利用浏览器提供的 IntersectionObserver,监听图片元素是否进入可视区域,进入后才真正去设置图片元素的 src 属性进行图片加载。

1. IntersectionObserver的基本使用

详细请看阮一峰IntersectionObserver API 使用教程

  • 作用:监听图片元素是否进入可视区域,进入后才真正去设置图片元素的 src 属性进行图片加载。
  • 格式:
var dom = dom元素
// 实例化一个观察者
// 它的参数1是一个回调:当被观察的目标进入视口/离开视口就会调用
var observer = new IntersectionObserver(  entries => {
    console.log(entries);
  }, 其他配置)

// 观察者观察dom
observer.observe(dom)
observer.disconnect()   // 停止观察者
observer.unobserve(dom) // 观察者停止对dom的观察
callback一般会触发两次。一次是目标元素刚刚进入视口(开始可见),另一次是完全离开视口(开始不可见)。
  • 参数1 :callback 参数
    • isIntersecting:当前DOM是否进入到视口 是一个布尔值
    • time:可见性发生变化的时间,是一个高精度时间戳,单位为毫秒
    • target:被观察的目标元素,是一个 DOM 节点对象
    • rootBounds:根元素的矩形区域的信息,getBoundingClientRect()方法的返回值,如果没有根元素(即直接相对于视口滚动),则返回null
    • boundingClientRect:目标元素的矩形区域的信息
    • intersectionRect:目标元素与视口(或根元素)的交叉区域的信息
    • intersectionRatio:目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0

  • 其他配置:
    • threshold 属性:threshold属性决定了什么时候触发回调函数。它是一个数组,每个成员都是一个门槛值,默认为[0],即交叉比例(intersectionRatio)达到0时触发回调函数。
    • root属性:指定目标元素所在的容器节点(即根元素)。注意,容器元素必须是目标元素的祖先节点。
    • rootMargin属性:后者定义根元素的margin,用来扩展或缩小rootBounds这个矩形的大小,从而影响intersectionRect交叉区域的大小。它使用CSS的定义方法,比如10px 20px 30px 40px,表示 top、right、bottom 和 left 四个方向的值。

2.示例代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

</head>

<body>
    <div>
        <p style="padding: 30px;">1</p>
        <p style="padding: 30px;">2</p>
        <p style="padding: 30px;">3</p>
        <p style="padding: 30px;">3</p>
        <p style="padding: 30px;">3</p>
        <p style="padding: 30px;">3</p>
        <p style="padding: 30px;">3</p>
        <p style="padding: 30px;">3</p>
        <p style="padding: 30px;">3</p>
        <p style="padding: 30px;">3</p>
        <p style="padding: 30px;">4</p>
        <p style="padding: 30px;">5</p>
        <p style="padding: 30px;">6</p>
        <p style="padding: 30px;">7</p>
        <p style="padding: 30px;">8</p>
        <p style="padding: 30px;">9</p>
        <!-- <img height="200" src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Ffzn.cc%2Fwp-content%2Fuploads%2F2020%2F04%2F640-8.jpg&refer=http%3A%2F%2Ffzn.cc&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1641365183&t=b5d7bdae0fe3f2c4831b52e3985abdf1" /> -->
        <img height="200"
            data-src="https://gimg2.baidu.com/image_search/src=http%3A%2F%2Ffzn.cc%2Fwp-content%2Fuploads%2F2020%2F04%2F640-8.jpg&refer=http%3A%2F%2Ffzn.cc&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1641365183&t=b5d7bdae0fe3f2c4831b52e3985abdf1" />
        <p style="padding: 30px;">1</p>
        <p style="padding: 30px;">2</p>
        <p style="padding: 30px;">3</p>
        <p style="padding: 30px;">4</p>
        <p style="padding: 30px;">5</p>
        <p style="padding: 30px;">6</p>
        <p style="padding: 30px;">7</p>
        <p style="padding: 30px;">8</p>
        <p style="padding: 30px;">9</p>
    </div>

    <script>
        // 获取DOM元素
        var img = document.querySelector("img")
        // 实例化 IntersectionObserver 
        var io = new IntersectionObserver(entries => {
            // 当被观察的目标进入视口/离开视口就会调用
            if (entries[0].isIntersecting) {//当前DOM是否进入到视口 布尔值
                console.log(img.getAttribute('data-src'));
                // 当前DOM进入到视口将自定义属性值给src
                img.src = img.getAttribute('data-src')
                img.onerror = function () { // 图片加载失败会被DOM的onerror方法捕获到
                    console.log('图片加载失败');
                }
                console.log(img.src);
                // 停止观察者
                io.disconnect()
            }
        }, { rootMargin: '100px' }); //DOM四周偏移100px (提前100px触发回调)
        // 观察者观察dom
        io.observe(img)
    </script>
</body>

</html>

3. 封装一个React懒加载组件

以下是搭配TS些的如果是js就之前删除类型即可 (type,<HTMLImageElement>…这一类)
scr/components/Image.tsx

import classnames from 'classnames'
import { useEffect, useRef, useState } from 'react'
import Icon from '../Icon'
import styles from './index.module.scss'

/**
 * 拥有懒加载特性的图片组件
 * @param {String} props.src 图片地址
 * @param {String} props.className 样式类
 */
type Props = {
    src: string
    className?: string
    alt?:string
}
const Image = ({ src, className, alt }: Props) => { // 封装Image懒加载图片组件
  const [upload, setUpload] = useState(true) // 加载中的变量等加载完后变成false
  const [lose, setLose] = useState(true) // 加载失败的变量

  // 对图片元素的引用
  const imgRef = useRef<HTMLImageElement>(null)
  useEffect(() => { // 副作用
    const ob = new IntersectionObserver((entries) => { // 实例化IntersectionObserver
      if (entries[0].isIntersecting) { // 当图片进入到视图
                imgRef.current!.src = imgRef.current?.getAttribute('data-src')!
                ob.unobserve(imgRef.current!) // 观察者停止对dom的观察
      }
    }, { rootMargin: '100px' })// 让图片提前100px加载
    ob.observe(imgRef.current!) // 观察者观察dom
    return () => {
      ob.disconnect() // 停止观察者
    } // 当组件销毁时停止观察者
  }, [])
  return (
        <div className={classnames(styles.root, className)}>
            {/* 正在加载时显示的内容 */}
            {
                upload && <div className="image-icon">
                    <Icon type="iconphoto" />
                </div>
            }

            {/* 加载出错时显示的内容 */}
            {
                !lose && <div className="image-icon">
                    <Icon type="iconphoto-fail" />
                </div>
            }

            {/* 加载成功时显示的内容 */}
            {<img
                onl oad={() => { setUpload(false) }} // 图片加载完执行
                one rror={() => setLose(false)} // 图片获取失败执行
                alt={alt}
                data-src={src}
                ref={imgRef} />
            }
        </div>
  )
}

export default Image

上一篇:使用IntersectionObserver进行曝光打点


下一篇:window的IntersectionObserver特性使用