Intersection Observer API
当页面上需要展示大量的图片时,只加载屏幕范围内我们看得见的图片
首先将 img
元素的 src
设置为默认占位的图片,设置自定义属性 data-src
为真实的图片地址
<div class="item">
<img
src="./default.png"
data-src="https://picsum.photos/400/600?r=1"
/>
</div>
监听这些元素与视口的相交情况
const ob = new IntersectionObserver(() => {
console.log('交叉改变后运行')
}, {
root: null, // 被观察对象的父级元素,默认是 null,表示视口
rootMargin: 0, // 扩张或收缩 root 的识别范围
threshold: 0,// 交叉的阈值 0-1 被观察对象交叉的程度
})
// 获取全部带有 data-src 属性的 img 元素
const imgs = document.querySelectorAll('img[data-src]')
imgs.forEach(() => {
ob.observe(img)
})
当元素出现在屏幕中时,替换 src 为 data-src
即可
const ob = new IntersectionObserver((entries) => {
// entries 是所有被观察元素的交叉情况
for(const entry of entries) {
if(entry.isIntersecting) { // entry.isIntersecting 是否相交
const img = entry.target // entry.target 被观察的元素
img.src = img.dataset.src // 替换 src
ob.unobserve(img) // 加载出来后就移除监听
}
}
}, {
threshold: 0,
})
// ...
其他方案
附上其他常见的图片懒加载方案
原生懒加载
利用 img 标签的原生属性 loading,添加懒加载功能
<img loading="lazy" src="image.jpg" alt="..." />
这个策略有一个动态的原则:
- Lazy loading加载数量与屏幕高度有关,高度越小加载数量越少,但并不是线性关系。4G 网络下的视口距离阈值是 1250 像素,3G 网络下是 2500 像素。
- Lazy loading加载数量与网速有关,网速越慢,加载数量越多,但并不是线性关系。
- 滚动会触发图片懒加载,不会说滚动一屏后再去加载。
- 窗口resize尺寸变化也会触发图片懒加载。
- 根据滚动位置不同,Lazy loading会忽略头尾的图片请求。
预览图
我在相册应用中使用了 thumbnail 作为预览图,占用空间小,传输效率高
在图片上传时,后端使用 sharp 生成一张预览图,将预览图与原图一同保存在服务器中
import sharp from 'sharp'
// 使用 sharp 生成预览图
await sharp(buffer)
.resize(200, 200)
.jpeg({ quality: 80 }) // 设置图片质量
.toFile(fileThumbPath)
模糊图
同样的思路,也可以生成一张模糊图作为父元素的背景图
<div class="blur-load rounded-lg" style="background-image: url(./1-sm.png)">
<img loading="lazy" alt="9" class="w-full object-cover rounded-lg" src="..." />
</div>
初始情况下,img 标签设置为透明。当图片加载完成(img.complete 或 load 事件触发)后,img 标签恢复显示
这利用了一个特性:img 图片的层级要高于其本身或者父级的背景图片,当图片加载出来后自动覆盖背景图片
function loaded(div) {
div.classList.add('loaded');
}
const blurDivs = document.querySelectorAll('.blur-load');
blurDivs.forEach(div => {
const img = div.querySelector('img');
// 在加载完成的时候添加样式
if (img.complete) { loaded(div) }
else { img.addEventListener('load', () => loaded(div)) }
})
/* 加载完成之前保持透明 */
.blur-load > img {
opacity: 0;
transition: opacity 200ms ease-in-out;
}
.blur-load.loaded > img {
opacity: 1;
}