今天想封装一个图片懒加载组件

555 阅读2分钟

1.目标

想封装一个拥有懒加载功能的图片组件,实现对文章列表项上的封面图片进行懒加载。

2.思路

当dom元素进入可视区域时,才去加载它。

如何判断一个dom元素是否进入了可见区域?

3.实现

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

4.格式

var dom = dom元素
// 实例化一个观察者
// 它的参数1是一个回调:当被观察的目标进入视口/离开视口就会调用
var observer = new IntersectionObserver((entries)=>{
  console.log(entries[0].isIntersecting)
  console.log(entries[0].intersectionRatio)
  if(entries[0].isIntersecting) {
    
  }
}, 其他配置)

// 观察者观察dom
observer.observe(dom)
// 停止观察者
observer.disconnect()  
// 观察者停止对dom的观察
observer.unobserve(dom) 

5.封装

components/LazyImage/LazyImage.tsx

使用 useEffect 在组件创建时和销毁时,监听图片元素一旦进入可视区域,就设置它的 src 属性进行加载:

import classnames from 'classnames'
import { useEffect, useRef, useState } from 'react'
import Icon from '../Icon'
import styles from './index.module.scss'
// 类型声明
type Props = {
  src: string
  className?: string
}
const LazyImage = ({ src, className, ...rest }: Props) => {
  // 对图片元素的引用
  const imgRef = useRef<HTMLImageElement>(null)
  const [loading, setLoading] = useState(true)
  const [error, setError] = useState(false)
  // 在 useEffect 里面操作
  useEffect(() => {
    const ob = new IntersectionObserver(
      (entries) => {
        console.log('working....')
        if (entries[0].isIntersecting) {
          imgRef.current!.src = src
          // 观察者停止对 img 的观察
          ob.unobserve(imgRef.current!)
        }
      },
      { rootMargin: '300px' } // 在距离图片 300px 的时候开始加载,提前准备好
    )
    // 观察 img
    ob.observe(imgRef.current!)
    return () => {
      // 停止观察者
      ob.disconnect()
    }
  }, [])

  return (
    <div className={classnames(styles.root, className)}>
      {/* ◆正在加载时显示的内容 */}
      {loading && (
        <div className="image-icon">
          <Icon type="iconphoto" />
        </div>
      )}

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

      {/* ◆加载成功时显示的内容 */}
      {
        <img
          {...rest}
          alt=""
          onError={() => setError(true)}
          onLoad={() => setLoading(false)}
          data-src={src}
          ref={imgRef}
        />
      }
    </div>
  )
}
export default LazyImage

6.测试

import LazyImage from '@/components/LazyImage/LazyImage'
// 省略其他
// <img src={imgSrc} alt=""/>
<LazyImage src={imgSrc} />