1.目标
想封装一个拥有懒加载功能的图片组件,实现对文章列表项上的封面图片进行懒加载。
2.思路
当dom元素进入可视区域时,才去加载它。
如何判断一个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} />