图片懒加载的原理
懒加载就突出一个懒字,只有当我们访问到图片资源时再去加载这张图片。这个时候就有小伙伴要问了,那什么时候叫访问到图片资源时?就是当图片出现在视口时,也就是浏览器可视区域内,我们再去加载这张图片,给<img />标签的src属性赋值为真实的图片地址。
图片懒加载实现思路
要想实现图片懒加载,我们需要先学会一个api:
IntersectionObserver
IntersectionObserver接口 (从属于Intersection Observer API) 提供了一种异步观察目标元素与其祖先元素或顶级文档视窗 (viewport) 交叉状态的方法。祖先元素与视窗 (viewport) 被称为**根 (root)。
这是MDN上对这这个api的描述。
以往我们想实现一个图片懒加载,只能使用getBoundingClientRect()获取图片节点的位置信息,添加滚动监听轮询节点的位置,这样就会导致每一次滚动事件触发时,都会执行大量的循环,这势必会对性能造成一定的影响。
现在,我们使用IntersectionObserver同时对多个图片进行观察,简单高效,整体效果如下:
具体实现
具体实现的话分两个部分
第一部分
第一是在所有懒加载图片公共父容器中将实例化一个观察器:
js
复制代码
const observer = new IntersectionObserver();
实例化时传入一个回调函数,第二个参数使用默认值就行。
js
复制代码
const observer = new IntersectionObserver((entries) => {
entries.forEach(item => {
if(item.intersectionRatio > 0) {
lazy(item.target);
}
});
});
其中的entries是一个触发回调事件的数组
一个
IntersectionObserverEntry对象的数组,每个被触发的阈值,都或多或少与指定阈值有偏差。
当intersectionRatio>0,也就是dom出现在浏览器视口时,调用lazy函数加载真实图片,使用useRef将该实例缓存下来
js
复制代码
const observer = useRef();
useEffect(() => {
observer.current = new IntersectionObserver((entries) => {
entries.forEach(item => {
if(item.intersectionRatio > 0) {
lazy(item.target);
}
});
});
return () => {};
}, []);
接下来就是lazy函数了,lazy函数接收一个dom,当dom内的img标签不存在src或者src有更新时,将最新图片地址赋值为真实地址:
js
复制代码
const lazy = (dom) => {
const imgDom = dom.children[0];
if(!imgDom.src || imgDom.src !== imgDom.dataset.src) {
imgDom.src = imgDom.dataset.src;
}
imgDom.onload = () => {
dom.style.backgroundColor = '';
dom.style.aspectRatio = '';
}
};
这里的children[0]为懒加载图片组件中的img标签,当图片加载完成时,取消懒加载组件的填充样式。app.jsx中完整代码如下:
js
复制代码
import React, { useEffect, useState, useRef } from 'react';
import './app.css';
import LazyImg from '../components/LazyImg';
const App = () => {
const observer = useRef();
const [imgs, setImgs] = useState([]);
useEffect(() => {
fetchData(1);
observer.current = new IntersectionObserver((entries) => {
entries.forEach(item => {
if(item.intersectionRatio > 0) {
lazy(item.target);
}
});
});
const lazy = (dom) => {
const imgDom = dom.children[0];
if(!imgDom.src || imgDom.src !== imgDom.dataset.src) {
imgDom.src = imgDom.dataset.src;
}
imgDom.onload = () => {
dom.style.backgroundColor = '';
dom.style.aspectRatio = '';
}
};
return () => {
// 取消所有图片懒加载组件的观察
observer.current.disconnect()
};
}, []);
const fetchData = (page) => {
// 请求图片数据
fetch('/src/pages/data.json')
.then(data => data.json())
.then(res => {
setTimeout(() => {
setImgs(imgs => {
return page === 1 ? res.data.oneImgs : imgs.concat(res.data.twoImgs);
});
}, 500);
});
};
return (
<ul className='app'>
<li><button onClick={() => {fetchData(2)}}>加载</button></li>
{
!!imgs.length && imgs.map((img, index) => {
return <li key={index}>
<LazyImg imgObj={img} index={index} observer={observer.current}/>
</li>
})
}
</ul>
)
}
export default App;
图片数据示例, 其中basicColor为懒加载组件填充色,width,height主要是获取宽高比
js
复制代码
{
"url": "https://dengxiang-image.oss-cn-shanghai.aliyuncs.com/images/01.jpeg",
"width": 353,
"height": 441,
"basicColor": "#a44a00"
},
第二部分
接着我们就要实现一个懒加载的图片组件,该组件包含展示的图片,和加载完图片前的填充样式。
我们需要将图片数据和观察器传入组件中,像这样:
js
复制代码
<LazyImg imgObj={img} index={index} observer={observer.current}/>
使用useRef获取图片组件,并使用传入的观察器进行观察
js
复制代码
const ref = useRef(null);
useEffect(() => {
if(ref.current && ref.current.children[0]) {
observer.observe(ref.current);
}
return () => {};
}, [ref]);
给图片组件添加填充样式,使用data-src保存真实的图片地址
js
复制代码
return (
<div ref={ref} className='img-box' style={{
aspectRatio: imgObj.width / imgObj.height,
backgroundColor: `${imgObj.basicColor}`
}}>
<img className='img' data-src={imgObj.url} />
<span className='index'>{index + 1}</span>
</div>
)
图片组件完整代码如下:
js
复制代码
import React, { useRef, useEffect } from 'react';
import './index.css';
export default function LazyImg({imgObj, index, observer}) {
const ref = useRef(null);
useEffect(() => {
if(ref.current && ref.current.children[0]) {
observer.observe(ref.current);
}
return () => {};
}, [ref]);
return (
<div ref={ref} className='img-box' style={{
aspectRatio: imgObj.width / imgObj.height,
backgroundColor: `${imgObj.basicColor}`
}}>
<img className='img' data-src={imgObj.url} />
<span className='index'>{index + 1}</span>
</div>
)
}