大家好,很高兴又见面了,我是”高级前端进阶”,由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发,您的支持是我不断创作的动力。
<img src="https://pic.songma.com/blogimg/20251118/154d7dfcc4f54be28bb63e8f098684c0.jpg" alt="2025 年你可能真的不知道如何用好标签?”>标签?” title=”2025 年你可能真的不知道如何用好
标签?”>
根据 HTTPArchive 数据,至少 70% 的网站图片位置超级突出,但只有 34% 的网站使用 <img srcset> 来创建响应式高性能图片,而使用 <picture> 的网站则更少。
本文将围绕响应式图片技术,介绍一种旨在使 <img> 标签像 <picture> 一样工作的新技术,从而更容易在现有代码库中进行大批量的迁移。
1. 为什么需要响应式图片 (Responsive Images)
一般来说,高质量的图片会带来更好的用户体验。
大屏幕和小屏幕,例如:桌面和移动设备,应该能显示不同的图片尺寸。手机的屏幕空间较小,因此需要更加注意可见的内容。同时,开发者还应该思考页面缩放级别,例如:当用户放大时,显示更高分辨率的图片是有意义的。
同时,开发者也不能忽视页面性能,较低的图片质量体积也较小,同时也受到文件格式的影响,最终影响页面加载速度和 Web Vitals 核心指标。通过减少加载的图片,则可以减少移动数据的浪费,确保为那些网速慢、设备老旧等的用户创造良好的体验。
因此,一个好的图片方案必须满足以下几个核心要点:
- 根据视口大小加载不同的图片尺寸,例如:桌面和移动设备
- 根据视口大小加载不同的图片质量
- 根据设备像素比 (DPR),缩放级别提供不同质量的图片
- 提供不同的文件格式,例如:WebP、AVIF 等
2. 一劳永逸的 <picture> 标签
<picture> 元素包含零个或多个 <source> 元素和一个 <img> 元素,用于为不同的显示器、设备场景提供最佳的图像加载方案。
浏览器会思考每个子 <source> 元素,并从中选择最佳匹配。如果没有找到匹配项(或者浏览器不支持 <picture> 元素),则选择 <img> 元素的 src 属性的 URL。最后,所选图像将显示在 <img> 元素占据的空间中。
<picture>
<source
// 媒体查询
media="(-webkit-min-device-pixel-ratio: 1.5)"
// srcset 指定图片路径和宽度描述符,对应于设备上的原始图像宽度
// corresponding to the original image width on your device
srcset="2x-800.jpg 800w, 2x-1200.jpg 1200w, 2x-1598.jpg 1598w"
// sizes 由媒体查询和插槽宽度组成,也可以使用 vw 表明宽度
sizes="
(min-width: 1066px) 743px,
(min-width: 800px) calc(75vw-57px),
100vw">
<img src="1x.jpg" alt="">
// 用于描述了图片及其渲染的大小
// 开发者还可以为其添加 alt 、loading="lazy" 等属性
</picture>
在上面的示例代码中,对于旧版本浏览器和低于 1.5x DPR 的屏幕将自动加载 1x.jpg 图片。至于其他屏幕,浏览器会根据视口宽度进行区分,例如:现代手机会加载 2x-800.jpg,台式机则加载 2x-1598.jpg。
为了确定要渲染哪个图片,<picture> 会告知浏览器明确加载开发者希望浏览器加载的内容,即 <source> 定义的内容)。因此,该方案很容易满足如下要求:
- 根据视口提供不同尺寸的图片
- 根据视口提供不同质量的图片
- 根据 DPR / 缩放级别提供不同质量的图片
- 提供不同的文件格式
从这点来看,开发者最好使用 <picture> 标签来获得响应迅速、高性能、清晰的图片。
不过,切换到 <picture> 最大的缺点就是放弃普通的 <img> 标签,而这意味着需要对现有代码库进行大量的重构。
3. 使用 <img srcset> 替代 <picture>
<picture> 元素的另一种替代方案是使用 <img srcset>。
srcset 属性是一个字符串,用于标识一个或多个图片候选字符串,这些字符串使用逗号 (,) 分隔,每个字符串指定在给定情况下要使用的图片资源。
每个图片候选字符串包含一个图片 URL 和一个可选的宽度 (width) 或像素密度描述符,用于指示应在何种条件下使用该候选图片,而不是使用 src 属性指定的图片。
srcset 以及 sizes 属性是设计响应式网站的关键,两者可以一起使用来制作适合渲染情况的图片的页面。
<img srcset> 和 <picture> 之间的细微差别是:使用 <img> 时,开发者将控制权交给浏览器,由浏览器负责确定最适合加载哪个图片。而由于 <img> 比 <picture> 更常用,因此大多数网站倾向于使用前者来显示响应式图片。
3.1 使用像素密度描述符 (Pixel Density Descriptors) 的方案
像素密度描述符的单位是 “x”,是 dots-per-px 的缩写(dppx) ,反映了设备 DPR。
<img srcset="medium.jpg, large.jpg 2x, ultra.jpg 3x" />
// 或者如下方式 (当浏览器需要 640px、960px 或 1024px 的图片时)
<img srcset="header640.png 640w, header960.png 960w, header1024.png 1024w">
在上面的代码示例中,标准显示器上的浏览器会加载 medium.jpg,而在更高分辨率的显示器上,例如:视网膜显示器、iPhone、现代 Android、4k 显示器等则会加载 large.jpg 或 ultra.jpg。
像素密度描述符 本质是使用一个已经存在了 10+ 年的 trick,即将图片设计为其渲染尺寸 (CSS 尺寸) 的 n 倍大小,例如:图片的固有尺寸为 400×400,而最终渲染尺寸为 200×200。由于浏览器显示时会自动缩小图片,因此看起来很清晰。
如果 “srcset” 中有资源使用了 “w” 描述符,则该 “srcset” 中的所有资源也必须使用 “w” 描述符,此时图片元素的 src 也不被视为候选资源。
<img srcset> 方案有优点也有缺点,例如:
- ❌ 根据视口提供不同尺寸
- ❌ 根据视口提供不同质量
- ☑️️ 根据 DPR / 缩放级别提供不同质量
- ❌ 可选:提供不同的文件格式
在移动设备上,无差别视口尺寸的图片加载会导致较长下载时间,继而降低网站性能,浪费用户流量,并通过 解码 + 渲染更大的图片 来增加 CPU 负担和增加电池使用量。
注意,开发者还可以使用 CSS 图片集在背景图片中使用密度描述符 image-set,但其也有自己的性能缺陷,因此不提议使用。
background-image: image-set("foo.png" 1x, "foo-2x.png" 2x);
3.2 只使用宽度描述符 width(Width descriptors) 的隐式像素密度方案
width 属性表明当图片被绘制或渲染到任何视觉媒体,例如:屏幕或打印机时,图片以 CSS 像素为单位 的绘制宽度。
- 如果图片被渲染到视觉媒体,则宽度以 CSS 像素表明
- 如果图片未渲染到视觉媒体,则其宽度使用图片的自然宽度表明,并根据 naturalWidth 调整视觉媒体显示密度
例如下面的示例,对于宽度不超过 400px 的视口,图片将以 200px 的宽度绘制。否则,将以 400px 的宽度绘制。
<p>Image width: <span class="size">?</span>px (resize to update)</p>
<img
src="/en-US/docs/Web/HTML/Element/img/clock-demo-200px.png"
alt="Clock"
srcset="
/en-US/docs/Web/HTML/Element/img/clock-demo-200px.png 200w,
/en-US/docs/Web/HTML/Element/img/clock-demo-400px.png 400w
"
// 显式指定 size
sizes="(max-width: 400px) 200px, 400px" />
接下来就开始介绍目前使用响应式 <img> 最流行的方式:
<img
src="img.jpg"
alt=""
sizes="(max-width: 63.9375em) 100vw,
(min-width: 64em) 750px"
// 显式指定 size
srcset="img/content/small.jpg 480w,
img/content/medium.jpg 750w,
img/content/large.jpg 1024w"/>
srcset 和 sizes 属性的工作方式与 <source> 标签完全一样,只是没有可用的 media 属性。在上面的代码片段中,为了确定最终的渲染图片,浏览器会使用第一个 size 媒体查询为 true 的结果。假设是 (min-width: 64em) 750px,则相当于告知浏览器渲染最小 750w 的宽度描述符,此时浏览器会加载 medium.jpg,而旧版浏览器会回退到 src。
值得一提的是,宽度描述符也适应了设备的 DPR。接下来一起看看,如果不显式指定 sizes 属性,那么会有什么问题呢?
// 宽度描述符适配 DRP
<img src="small.jpg" srcset="medium.jpg 1000w, large.jpg 2000w" />
根据规范,浏览器会将 宽度描述符除以 <img> 元素的 “源尺寸”,以确定每幅图片的像素密度,然后将其与设备的 DPR(包括缩放级别)进行比较。“源尺寸” 由 sizes 属性定义,如果省略,则默认为 100vw,即当前屏幕宽度。
Firefox 和 Safari 浏览器会选择大于当前 DPR 的密度(如果不可能,则选择最高密度)来确定渲染的图片。Chromium 对 DPR <= 1x 执行一样操作,但对于 DPR> 1x,浏览器会检查值之间的几何平均来确定要加载的图片:
if (next_density < DPR) { continue; /* 跳过下一个 srcset 入口 */ }
if ((DPR <= 1 && DPR> current_density)
|| DPR >= geometric_mean(current_density, next_density))
return next;
return current;
根据经验法则,对于 DPR > 1x,Chromium 浏览器会选择最接近(srcset 描述的宽度)视口宽度的图片。接下来看一个视口宽度为 360px 的例子,浏览器计算规则如下:
- 1000w / 360px = 2.8x
- 2000w / 360px = 5.6x
DPR 为 3x:
- Firefox 和 Safari 从 slot large.jpg 2000w 中选择密度 5.6x
- Chrome:
- 计算几何平均值:Math.sqrt(2.8*5.6) = 4x
- 3x >= 4x 为 false,因此选择 medium.jpg 1000w
总而言之,由于 宽度描述符隐式代表像素密度描述符,且开发者一般也更加青睐后者。不过,该行为也带来了一些怪异:
- 第一,由于浏览器的差异且规范将最终图片的决定权留给浏览器。在不同 DPR 、视口宽度、设备等场景下可能加载一样图片,即使在手机上显示得小得多。因此,使用隐式机制会消除显式设置视口大小的能力。
- 其次,这种方法会导致性能问题,WhatWG 也承认了这一点
33% 的桌面页面会加载超过 83 KB 的多余图片数据,即 srcset 中有一个更好、更小的资源可供使用,但由于 size 属性错误而错过。此外,10% 使用 size 的桌面页面由于 size 属性错误而加载了超过 0.5 MB 的多余图片数据!
总的来说,<img srcset> 方法有以下优势和不足:
- ☑️️ 根据视口提供不同尺寸
- ☑️️ 根据视口提供不同质量
- ☑️️ 根据 DPR / 缩放级别提供不同质量,但是是隐式设置
- ❌ 可选:提供不同的文件格式
4. 使用布尔值的 <img> 去除隐式像素密度方案
根据前文的学习,开发者可以设置一个 <img> 标签,其结合了 size 和 srcset 的宽度描述符以及隐式密度计算,其行为就像一个 <picture> 元素:
<img
src="1x.jpg"
// size 属性
sizes="
(max-width: 480px) and (-webkit-device-pixel-ratio: 1) 1px,
(min-width: 481px) and (-webkit-device-pixel-ratio: 1) 2px,
(max-width: 480px) and (-webkit-min-device-pixel-ratio: 2) 3px,
(min-width: 481px) and (-webkit-min-device-pixel-ratio: 2) 16px"
// srcset 属性
srcset="
medium-1x-q75.jpg 1w,
large-1x-q75.jpg 2w,
medium-2x-q35.jpg 15w,
large-2x-q50.jpg 16w"
/>
通过结合 size 和 srcset(包括:宽度描述符),开发者重新获得了对浏览器图片加载的控制权。如前文所述,宽度描述符是隐式工作的,因此还特地引入了 size 属性,该属性针对单个 DPR 以将其明确化。此时 <img> 标签表现与 <picture> 超级类似,从而可以有条件地加载不同尺寸和不同质量的图片。
这种方式可以不改变 DOM 结构,最终显著减少所需的开发时间。当在大型代码库中 “hotfix” 模糊图片时超级重大。
4.1 size 属性
如前文所述,size 属性允许开发者使用 CSS 媒体查询,这意味着其不受最小和最大宽度的限制。此外,Andrea Verlicchi 和 Cloudinary 等也研究了为什么一般不需要区分 2.5 、3 倍甚至更高的分辨率。
对于属性中的条件,一般可以用:
- 480px 作为断点来区分手机和其他设备类别
- 使用 -webkit-device-pixel-ratio 和 -webkit-min-device-pixel-ratio 区分 1x DPR 和 >= 2x DPR 屏幕
即使在非 WebKit 浏览器中,-webkit-(min)-device-pixel-ratio 也支持的很好。而如果仅支持 Safari 16+,则可以将前缀替换为 (resolution: 1dppx) 和 (min-resolution: 2dppx)。
实则,开发者只要记住 浏览器只会选择第一个值为 true 的条件,就可以像 if-else 一样编写条件语句:
- 如果屏幕宽度 <= 480px && DPR == 1:加载插槽 1
- 如果屏幕宽度 >= 481px && DPR == 1:加载插槽 2
- 如果屏幕宽度 <= 480px && DPR>= 2:加载插槽 3
- 如果屏幕宽度 >= 481px && DPR >= 2:加载插槽 16
每个查询的值不再是显示的图片宽度,而是定位的 srcset 插槽。换句话说,删除了 “图片宽度 (image width)” 和 “宽度描述符 (width descriptor)” 之间的近似相关性,而是使用数字来定位 srcset 的特定插槽。
4.2 srcset 属性
如前所述,目前使用来自 sizes 的 px 来设置 “源尺寸”,以便定位 srcset 的特定插槽。结合 “宽度描述符” 内容中提到的隐式密度计算知识,相当于创建了 srcset 映射,诱使浏览器从真实尺寸条件下载图片,即下一个最大的插槽(或最低几何平均值)。
接下来看一个 DPR 为 3x 且屏幕宽度为 360px 的示例:
- 对于 sizes 属性,(max-width: 480px) 和 (-webkit-min-device-pixel-ratio: 2) 3px 是第一个执行为 true 的媒体查询,此时浏览器将 “源尺寸” 设置为 3px
- 接下来,浏览器需要定位目标 srcset 插槽
- 密度:
- 1w / 3px = 0.33x,2w / 3px = 0.67x
- 15w / 3px = 5x,16w / 3px = 5.33x
- 所有浏览器选择的插槽都是 medium-2x-q35.jpg 15w,由于:
- 下一个最大密度(Firefox、Safari):来自插槽 15w 的 5x
- Chromium:计算几何平均值:Math.sqrt(5* 5.33) = 5.16x3x >= 5.16x 为假,因此选择插槽 15w
未来可能会有 5x DPR 的设备,因此为了防止图片模糊,需要确保两个 2x DPR 大小值都乘以 5,以便 Safari 和 Firefox 选择正确的 srcset 值(3*5 => 下一个最大值是 15w),而这只会在大于 5x 的屏幕出现之前起作用。
size 和 srcset 中的最后一个插槽基本上是 if-else 链中的 else,这意味着开发者可以简单地 +1 来创建最后一个插槽,供 其他设备 && DPR >= 2 类别使用。
最后,通过使用 src 属性为无法识别任何操作的浏览器设置默认值。如果没有媒体查询匹配,则默认值为 100vw。
5. 关于 “布尔”<img> 的结论
通过使用这种方法,可以安全地判断哪个浏览器或设备渲染了哪个图片。
虽然 <picture> 比这种方法更明确,但使用 “布尔”<img> 标签的工作量比通过引入其他 HTML 标签等方式,例如:更新与 JavaScript 事件和 CSS 相关的依赖项,进行重构要小的多。相比之下,添加或更新 srcset 和 size 这两个属性很容易。
“布尔”<img > 的优点和缺点如下:
- ☑️️ 根据视口提供不同的尺寸
- ☑️️ 根据视口提供不同的质量
- ☑️️ 根据 DPR / 缩放级别提供不同的质量——明确
如果将其自动化,开发者甚至不需要手动制作媒体查询。以下数学公式可以在任何地方实现,其中 N、M 是每个类别的图片宽度,A 是断点号:
<img
sizes="
(max-width: Apx) and (resolution: 1dppx) Npx,
(min-width: (A+1)px) and (resolution: 1dppx) Mpx,
(max-width: Apx) and (min-resolution: 2dppx) (M+1)px,
(min-width: (A+1)px) and (min-resolution: 2dppx) (((M+1)*5)+1)px"
srcset="
low-dpr-xs.jpg Nw,
low-dpr-xl.jpg Mw,
high-dpr-xs.jpg ((M+1)*5)w,
high-dpr-xl.jpg (((M+1)*5)+1)w"
src="fallback.jpg"
alt="don't forget the alt attribute"
/>
参考资料
https://kurtextrem.de/posts/modern-way-of-img#pixel-density-descriptors
https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/srcset
https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/width
https://css-tricks.com/responsive-images-youre-just-changing-resolutions-use-srcset/
https://html.spec.whatwg.org/multipage/images.html#update-the-source-set
https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/sizes
https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture



ie6时期 img 可以挂病毒