优化大量图片的加载
📊 对比表
优化方式 | 实现方式 | 优点 | 注意点 | 适用场景 |
---|---|---|---|---|
渐进式加载(懒加载 + 占位图/LQIP) | loading="lazy" / IntersectionObserver + 占位图(灰块或缩略图) | 节流带宽,避免白屏,平滑过渡 | 首屏关键图不要延迟;占位图必须真实存在 | 长列表、首屏大图、轮播 |
预加载 (Preload) | 当前图前后各 1 张:<link rel="preload"> + new Image() | 提升切换体验 | 避免预热过多,注意清理旧标签 | 轮播图 / 相册 |
压缩与格式优化 | SVGO 压缩 SVG、TinyPNG 压 PNG/JPG、WebP/AVIF 转换 | 极大降低体积 | 保留回退格式,考虑兼容性 | 图标、插画、摄影图 |
按需裁剪 | 服务端/CDN 输出与展示尺寸匹配的图片 | 避免下载无用大图 | 结合 srcset/sizes ;注意高分屏支持 | 商品列表、缩略图展示 |
1) 渐进式加载(懒加载 + 占位图)
HTML
<!-- 占位图可以是极小缩略图,保证不白屏 -->
<img
class="lazy"
src="/img/hero@24w.jpg"
data-src="/img/hero-1600.jpg"
data-srcset="/img/hero-800.jpg 800w, /img/hero-1600.jpg 1600w"
sizes="(max-width: 768px) 100vw, 768px"
alt="示例图片"
decoding="async"
/>
CSS
img {
width: 100%;
height: 100%;
}
img.lazy {
filter: blur(16px);
transform: scale(1.02);
transition: filter .4s ease, transform .4s ease;
}
img.lazy.is-loaded {
filter: blur(0);
transform: none;
}
JS
const loadRealImage = (img) => {
if (img.dataset.srcset) img.srcset = img.dataset.srcset;
if (img.dataset.src) img.src = img.dataset.src;
img.decode?.().catch(()=>{}).finally(() => {
img.classList.add('is-loaded');
});
};
const imgs = document.querySelectorAll('img.lazy');
const io = new IntersectionObserver((entries) => {
entries.forEach(e => {
if (e.isIntersecting) {
loadRealImage(e.target);
io.unobserve(e.target);
}
});
}, { rootMargin: '300px 0px' });
imgs.forEach(img => io.observe(img));
✅ 要点:
- 占位图必须真实存在(灰块或 tiny 缩略图)。
- 首屏关键图不要延迟,可加
fetchpriority="high"
。 - 必须写
width/height
或aspect-ratio
,避免 CLS 抖动。
2) 图片预加载(前后各一张)
当当前索引 i
变化时,只预加载 i-1
与 i+1
。动态创建 <link rel="preload" as="image">
,并配合 new Image()
触发缓存;同时\清理旧的预加载标签
const slides = ['/img/1.jpg','/img/2.jpg','/img/3.jpg'];
function preloadNeighbors(currentIndex) {
document.querySelectorAll('link[data-preload]').forEach(l => l.remove());
const neighbors = [currentIndex - 1, currentIndex + 1]
.filter(i => i >= 0 && i < slides.length);
neighbors.forEach(i => {
const url = slides[i];
const link = document.createElement('link');
link.rel = 'preload';
link.as = 'image';
link.href = url;
link.dataset.preload = true;
document.head.appendChild(link);
const im = new Image();
im.src = url;
});
}
let current = 0;
function goTo(i) {
current = i;
// 展示 slides[i]
preloadNeighbors(current);
}
goTo(0);
✅ 要点:
- 只预加载前后各一张,不要一次性全部预热。
- 每次切换时清理旧的
<link>
,避免内存占用。
3) 压缩与格式优化
- SVG:使用 SVGO 压缩。
- PNG/JPG:使用 TinyPNG 压缩。
- WebP/AVIF:体积更小,清晰度更高。
前端 <picture>
回退:
<picture>
<source type="image/avif" srcset="/img/hero.avif">
<source type="image/webp" srcset="/img/hero.webp">
<img src="/img/hero.jpg" alt="示例" width="1200" height="675" loading="lazy">
</picture>
✅ 要点:
- AVIF 最省流量但编码慢;WebP 兼容性更好。
- 保留回退 JPG/PNG,避免旧浏览器加载失败。
4) 按需裁剪(服务端/CDN Resize)
前端自适应:
<img
src="/img/p-800.jpg"
srcset="/img/p-400.jpg 400w, /img/p-800.jpg 800w, /img/p-1200.jpg 1200w"
sizes="(max-width: 600px) 100vw, 600px"
alt="示例"
width="600" height="400"
/>
CDN 示例:
- 阿里云 OSS:
/img/p.jpg?x-oss-process=image/resize,w_600
- 七牛云:
/img/p.jpg?imageMogr2/thumbnail/600x
✅ 要点:
- 服务端/CDN 生成与展示区域接近的尺寸。
- 配合
srcset/sizes
让浏览器选择最合适的一张。 - 注意高分屏 DPR(2x/3x)支持。
🎯 总结
- 渐进式加载(懒加载 + 占位图/LQIP):节省流量、避免白屏。
- 预加载:提升切换体验。
- 压缩与格式:降低体积,兼容多端。
- 按需裁剪:避免下载大图,节省带宽。
👉 推荐组合:
- 首屏大图 → 渐进式加载(tiny 占位图)
- 轮播/相册 → 渐进式加载 + 前后预加载
- 图标/插画 → SVG 压缩
- 商品缩略图 → 服务端裁剪 +
srcset