React Hooks 实现图片懒加载useLazyLoad


定义useLazyLoad

思路: 判断图片在视口内就加载,即: 元素距离页面顶部的距离offsetTop < 滚动条高度scrollTop + 视口高clientHeight

import { useCallback, useEffect, useState } from 'react'
import { ImgDOMListType, ImageListType } from '../components/LazyLoadDemo'
import useThrottle from './useDebounce'

interface LazyLoadPropsType {
    domList: ImgDOMListType,
    imgList: ImageListType,
    throttle: number
}

const useLazyLoad = ({ domList, imgList, throttle }: LazyLoadPropsType) => {

    const [scrollCount, setScrollCount] = useState(0)

    // 节流
    const loadImg = useThrottle(() => {
        domList.forEach((el, i) => {
            // 已经加载的无需继续加载
            if (!el || imgList[i].loaded) return
            // 加载条件:判断元素在视口内,即图片距离页面顶部的距离 offsetTop < 滚动条高度+视口高
            if (el.offsetTop < document.documentElement.scrollTop + document.documentElement.clientHeight) {
                el.src = el.dataset.src as string
                imgList[i].loaded = true
            }
        })
    }, throttle, [domList, imgList, throttle])

    const beginLoad = useCallback(() => setScrollCount(v => v + 1), [setScrollCount])

    // 监听scroll事件
    useEffect(() => {
        document.addEventListener('scroll', beginLoad)
        return () => document.removeEventListener('scroll', beginLoad)
    }, [beginLoad])

    // 开始计算懒加载图片位置
    useEffect(() => {
        loadImg()
    }, [loadImg, scrollCount])

}

export default useLazyLoad

测试用例

import { useRef } from "react"
import useLazyLoad from "../hooks/useLazyLoad"

import './LazyLoadDemo.css'

export type ImageListType = Array<{
    id: number | string,
    src: string,
    loadingSrc: string,
    loaded: boolean
}>
export type ImgDOMListType = Array

const loadingSrc = 'https://www.maojiemao.com/public/svg/gen1.png'
const src = 'https://www.maojiemao.com/public/svg/gen2.png'

const imgList: ImageListType = Array.from({ length: 30 }).map((v, i) => ({
    id: i,
    src,
    loadingSrc,
    loaded: false
}))

const LazyLoadDemo: React.FC = () => {
    const domRef = useRef([])
    useLazyLoad({ domList: domRef.current, imgList, throttle: 200 })

    return (
        
{ imgList.map((v, i: number) => (domRef.current[i] = el)} src={v.loadingSrc} data-src={v.src} alt="" className="item" /> ) }
) } export default LazyLoadDemo