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<HTMLImageElement | null>
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<ImgDOMListType>([])
useLazyLoad({ domList: domRef.current, imgList, throttle: 200 })
return (
<div className="wrapper">
{
imgList.map((v, i: number) =>
<img
key={i}
ref={(el) => (domRef.current[i] = el)}
src={v.loadingSrc}
data-src={v.src}
alt=""
className="item" />
)
}
</div>
)
}
export default LazyLoadDemo