1.为什么使用图片懒加载?
为了解决 一进入页面,所有的图片都会一次性加载,这可能会造成网络资源和降低用户的体验。
比如某宝App打开之后会加载很多商品预览图片,每个图片都是一个请求,那么刚进入页面就发很多请求会造成性能的浪费,从用户的角度来看会给用户一种卡顿的感觉极其影响购物体验
所以使用 图片懒加载,只有当用户滚动页面,真正的浏览的到这些列表项 ,才开始加载那些图片
2.原理
3.原生Api代码实现
Intersection ObserverMDN官方文档
利用浏览器提供的 intersectionObserver
Api,监听图片元素是否进入到可视区域,进入之后才真正的去设置图片元素的 src
属性进行图片加载。还额外针对图片加载是否出错做了处理。如果图片加载出错,显示约定好的图片。
- 图片不直接设置src,通过data-src属性设置图片地址,直接设置一进页面就会加载
- 当图片进入可视区后在进行加载
- 通过
IntersectionObserver
监听进入可视区域 - 如果进入可视区域后将data-src中存储的地址赋值给src
原生demo:
<body>
<div style="height: 3000px; background-color: rgb(194, 89, 89)"></div>
<img data-src="https://img.svsva.cn/20220204/BG3ZzqNk.png" alt="" />
<script>
// 原理:
// 首先不会直接给img设置图片路径,因为直接设置会进入页面就加载
// 通过data-img设置图片路径,当图片进入可视区域后在加载
// 使用 原生 IntersectionObserver的Api进行监听 等进入可视区域后再将data-src图片地址赋值给img的src属性
// 找到需要监听的元素
const img = document.querySelector('img')
const io = new IntersectionObserver(([{ isIntersecting, target }]) => {
console.log(isIntersecting)
console.log(target)
if (isIntersecting) {
target.src = target.dataset.src
// 如果已经加载完毕了 就可以解绑事件监听了
io.unobserve(img)
}
})
io.observe(img)
</script>
</body
React封装组件:
type Props = {
src: string
className?: string
}
/**
* 拥有懒加载特性的图片组件
* @param {String} props.src 图片地址
* @param {String} props.className 样式类
*/
const Image = ({ src, className }: Props) => {
// 记录图片加载是否出错的状态
const [isError, setIsError] = useState(false)
// 记录图片是否正在加载的状态
const [isLoading, setIsLoading] = useState(true)
// 对图片元素的引用 类型设置
const imgRef = useRef<HTMLImageElement | null>(null)
// 组件收到src后 先储存起来
// 建立监听
useEffect(() => {
const io = new IntersectionObserver(([{ isIntersecting, target }]) => {
// 如果isIntersecting = true 代表进入了可视区域
if (isIntersecting) {
// 获取Dom元素 ! 非空断言
const imgDom = imgRef.current!
// 进入之后将data-src的值赋值给src
imgDom.src = imgDom.dataset.src!
// 设置之后结束特定监听
io.unobserve(imgDom)
}
//组件卸载时 触发全部停止监听
return () => {
io.disconnect()
}
})
// 开启监听
io.observe(imgRef.current!)
})
return (
<div className={classnames(styles.root, className)}>
{/* 正在加载时显示的内容 */}
{isLoading && (
<div className="image-icon">
<Icon type="iconphoto" />
</div>
)}
{/* 加载出错时显示的内容 */}
{isError && (
<div className="image-icon">
<Icon type="iconphoto-fail" />
</div>
)}
{/* 加载成功时显示的内容 */}
{!isError && (
<img
alt=""
data-src={src}
ref={imgRef}
onLoad={() => setIsLoading(false)}
onError={() => setIsError(true)}
/>
)}
</div>
)
}