手机上打开页面,第一屏图片加载了 2.8MB。
我第一眼就不太信业务代码有多复杂,先开 DevTools,看 Network,果然一张商品图在 390px 宽的屏幕上,请求的是 2400px 的大图。页面慢,不是接口慢,也不是 JS 执行慢,就是图片喂太狠了。
这时候就该看 img 的 srcset。
srcset 不是给图片加备用地址那么简单。它的作用是:把同一张图的多个规格告诉浏览器,让浏览器自己根据屏幕宽度、设备像素比、sizes 规则去挑一张最合适的图。
别小看“浏览器自己挑”这几个字。图片这块你用 JS 硬算,八成越写越别扭,还容易算错。
最常见的写法长这样:
<img src="/img/goods-800.jpg" srcset=" /img/goods-400.jpg 400w, /img/goods-800.jpg 800w, /img/goods-1200.jpg 1200w " sizes="(max-width: 600px) 92vw, 520px" alt="商品主图" />
这里别急着背属性,直接看浏览器怎么判断。
如果屏幕宽度小于 600px,这张图在页面里大概占 92vw,也就是视口宽度的 92%。
假设手机视口是 390px,那么图片展示宽度大概是:
const viewport = 390; const displayWidth = viewport * 0.92; console.log(displayWidth); // 358.8
但浏览器不会只看 CSS 宽度,还会看设备像素比。
比如 iPhone 上 devicePixelRatio 可能是 3,那它真正希望拿到的图片资源宽度大概是:
const cssWidth = 390 * 0.92; const dpr = 3; const expectImageWidth = Math.ceil(cssWidth * dpr); console.log(expectImageWidth); // 1077
这时候候选图里有 400、800、1200。
浏览器大概率会选 1200w,因为 800w 在高 DPR 屏幕上可能会糊,1200w 更接近需要的清晰度。
这就是 srcset 干的事。
不是你写一堆判断:
const img = document.querySelector('#goods-cover');
const width = window.innerWidth;
const dpr = window.devicePixelRatio || 1;
if (width * dpr <= 400) {
img.src = '/img/goods-400.jpg';
} else if (width * dpr <= 800) {
img.src = '/img/goods-800.jpg';
} else {
img.src = '/img/goods-1200.jpg';
}这种代码我一般不爱放线上。
不是不能用,是它管得太多。屏幕旋转怎么办?布局变化怎么办?图片不是满屏而是卡片里 180px 宽怎么办?用户浏览器已经有选择算法了,你非要自己接管,后面维护的人会骂。
更靠谱的是让 HTML 把规则说清楚:
<img src="/img/article-cover-800.jpg" srcset=" /img/article-cover-360.jpg 360w, /img/article-cover-720.jpg 720w, /img/article-cover-1080.jpg 1080w " sizes=" (max-width: 480px) 94vw, (max-width: 900px) 70vw, 640px " alt="文章封面" />
这段里我最关心的不是 srcset,而是 sizes。
因为 srcset 只是告诉浏览器“我手里有哪些图”。sizes 才是告诉浏览器“这张图最终大概要显示多宽”。
如果你只写 srcset,不写 sizes,浏览器会按默认的 100vw 去估算。这个坑挺常见。
比如图片实际在右侧卡片里,只有 300px 宽,但你没写 sizes,浏览器以为它要占满整屏,可能就选了一张更大的图。
看着没报错,Network 里全是浪费。
还有一种写法是按像素倍率写:
<img src="/img/avatar@1x.png" srcset=" /img/avatar@1x.png 1x, /img/avatar@2x.png 2x, /img/avatar@3x.png 3x " width="80" height="80" alt="用户头像" />
这个适合头像、图标、小尺寸固定图片。
比如头像永远就是 80px,不随屏幕变来变去,那就没必要写 400w、800w 这种宽度描述,直接告诉浏览器:普通屏用 1x,高清屏用 2x 或 3x。
这里也别贪。
头像 80px,你上来塞一张 2000px 的原图,然后指望浏览器自己压缩显示,页面是能显示,但流量和解码成本都吃亏。
我一般会用一段小脚本扫一下页面上的图片,先把离谱的找出来:
const badImages = [...document.images]
.map((img) => {
const box = img.getBoundingClientRect();
return {
src: img.currentSrc || img.src,
showWidth: Math.round(box.width),
realWidth: img.naturalWidth,
wasteRatio: img.naturalWidth && box.width
? Number((img.naturalWidth / box.width).toFixed(2))
: 0
};
})
.filter(item => item.wasteRatio > 3);
console.table(badImages);这个脚本不是生产代码,就是排查用。
重点看 currentSrc。用了 srcset 以后,img.src 不一定能说明浏览器最后选了哪张图,currentSrc 才更接近现场。
比如你看到日志是这样:
{
src: "https://static.xxx.com/goods-1200.jpg",
showWidth: 360,
realWidth: 1200,
wasteRatio: 3.33
}这就要回去看两个地方。
一个是候选图是不是缺了中间规格。 一个是 sizes 写得是不是太大。
比如这个就不太对:
<img src="/img/goods-800.jpg" srcset=" /img/goods-800.jpg 800w, /img/goods-1600.jpg 1600w " sizes="100vw" alt="商品图" />
移动端卡片图实际只有半屏宽,你写 100vw,浏览器不选大图才怪。
改成更贴近布局的:
<img src="/img/goods-800.jpg" srcset=" /img/goods-360.jpg 360w, /img/goods-720.jpg 720w, /img/goods-1080.jpg 1080w " sizes=" (max-width: 600px) 46vw, 280px " alt="商品图" />
这才像是在跟真实页面对齐。
srcset 还有一个容易误会的点:它不是懒加载。
懒加载是控制什么时候加载:
<img loading="lazy" src="/img/list-720.jpg" srcset=" /img/list-360.jpg 360w, /img/list-720.jpg 720w " sizes="(max-width: 600px) 48vw, 260px" alt="列表图片" />
srcset 是控制加载哪一张。
这两个可以一起用,但别混成一个概念。线上排性能问题时,这两个方向也要分开看。首屏慢,先看首屏图是不是太大;长列表滚动卡,再看懒加载和图片解码。
还有兜底问题。
src 不能省。老浏览器、爬虫、某些特殊环境不一定完整支持 srcset,src 至少能保证图片还能显示。
<img src="/img/banner-960.jpg" srcset=" /img/banner-480.jpg 480w, /img/banner-960.jpg 960w, /img/banner-1440.jpg 1440w " sizes="100vw" alt="活动 Banner" />
这段里,src="/img/banner-960.jpg" 就是兜底资源。
真要验证 srcset 有没有生效,不要只看页面肉眼清不清晰。肉眼不靠谱,尤其是设计图本来就糊的时候。
直接看浏览器最终选了谁:
document.querySelectorAll('img').forEach((img) => {
img.addEventListener('load', () => {
console.log('[img-loaded]', {
alt: img.alt,
currentSrc: img.currentSrc,
naturalWidth: img.naturalWidth,
clientWidth: Math.round(img.getBoundingClientRect().width),
dpr: window.devicePixelRatio
});
});
});这段加到本地调试环境里,刷新页面,多切几个设备尺寸,基本就能看出问题。
srcset 的价值不在于写法多高级,而在于把图片选择这件事从 JS 和后端模板里拿出来,交给浏览器按真实环境处理。
屏幕宽度、DPR、布局宽度、缓存状态、网络条件,这些东西浏览器比我们更接近现场。
你给它足够准确的候选图和 sizes,它通常会选得比手写判断稳。
图片性能问题很多时候不吵不闹,接口 80ms,JS 也没报错,页面就是慢。最后一查,是移动端加载了桌面大图。
这种问题,srcset 能挡掉不少。








