用 Eleventy Img 升级响应式图片
所属系列
- Subspace Builder 系列 (3 共 9)
最初的问题
我发现,小屏幕上的截图看起来变形了,尽管原始素材本身很清晰。大概就是这样……

Markdown 文章使用普通 <img> 标签,只指定了 height="300"。布局变窄时,浏览器会缩小图片宽度以适应内容栏,这是 Tachyons 默认 img { max-width: 100%; } 的行为,但_通过属性设置_的高度仍锁定在 300 像素。浏览器将显式 HTML 属性的优先级视为高于 CSS 后备值,所以为了满足固定高度,图片在垂直方向被拉伸。移除写死的高度,变形就消失了。
我们希望解决方案能够:
- 无需在 Markdown 中手动填写尺寸,也能保持宽高比。
- 向移动设备提供更小的文件。
- 为 WebP、AVIF 等现代格式打好基础,不用手写
<picture>标记。
于是,Eleventy Img 登场了。
目录
为什么选择 @11ty/eleventy-img
这个插件只需很少配置,就能生成可用于生产的响应式图片:
- 从一张源图生成多种宽度和格式,包括 WebP、JPEG,以及需要时的 AVIF。
- 在构建时压缩,让浏览器下载更少的数据。
- 缓存输出,源图和配置都没变时跳过重复处理。
- 输出正确标记,包括
<picture>、srcset、sizes和宽高属性,保留图片固有比例,减少布局偏移。 - 方便未来升级:启用新格式,只需在 formats 数组中多加一项。
我们如何接入 Eleventy Img
最初,我们把 Markdown 中的 <img> 换成 {% image %} 短代码。虽然可行,但作者每次放截图都得记住一个自定义标签。后来发现,更适合这个项目的是 Eleventy Img 的 HTML 转换插件。
转换插件扫描渲染后的 HTML,找到每一个 <img>,再将其改写为完整的 <picture> 元素。我们的配置(eleventy.config.js)设置了:
formats: ["avif", "webp", "jpeg"],让现代浏览器优先拿到更小的文件。widths: [320, 640, 960, 1280],覆盖内容栏的半倍、1 倍和 2 倍尺寸。- 在
htmlOptions.imgAttributes中设置loading="lazy"、decoding="async",以及与布局对应的sizes字符串((width <= 30em) 100vw, 75vw):手机上占满屏宽,达到 Tachyons 的-ns断点后,大约占视口宽度的 75%。
本地开发时,Eleventy 通过按需处理的 /.11ty/image/ 端点提供派生图片;生产构建则把最终文件写入 _site/img/。由于转换发生在 Markdown 渲染之后,作者可以继续使用普通 HTML 或 Markdown 图片,同时享受响应式标记带来的好处。
响应式图片标记如何工作
主要靠两个属性:
srcset列出候选文件,每个带一个宽度描述符,例如640w。sizes告诉浏览器,图片在布局中会显示多宽。如果省略,浏览器会假定为100vw,可能下载过大的图片。
经典媒体查询语法
sizes="(max-width: 600px) 100vw, 600px"
从左往右读:视口宽度不超过 600px 时,图片占满视口宽度;否则,将宽度限制在大约 600px。
范围语法(Media Queries Level 4)
sizes="(width <= 37.5em) 100vw, 37.5em"
较新的范围语法用类似数学的比较替代 max-width。使用 em,可以让断点与文字大小关联起来;默认基准为 16px 时,37.5em 约等于 600px,用户缩放页面时也能更自然地适配。
选择合适的宽度
确定最大显示宽度后,再生成几个有用的派生尺寸。一个简单的经验是最大值的 0.5 倍、1 倍和 2 倍。我们的内容栏宽度大约为 640px,因此生成 [320, 640, 960, 1280]。Eleventy Img 会自动舍弃超过源图尺寸的选项,所以可以多给一些较大的尺寸,不必担心放大后模糊。
这些宽度进入 srcset 后,浏览器会根据当前设备像素比和视口宽度,选择仍足够清晰的最小文件。再配上合适的 sizes,手机用户下载的数据会大幅减少,视网膜屏幕也仍然能得到清晰图片。
确保宽高比正确的保护措施
- 始终提供有意义的 alt 文本。忘记时 Eleventy Img 会报错,避免把无障碍缺陷带到线上。
- 保留生成的
width和height属性;它们描述最大派生图的固有尺寸,浏览器据此预留布局空间。 - 如果文章需要针对不同屏幕调整构图,比如手机上裁成正方形,就使用短代码,在
<picture>中按断点指定来源。转换插件很适合默认情况,但处理这些特殊需求不够灵活。 - 我们加了一条小小的 CSS 覆盖(
img { height: auto; }),确保窄内容栏里图片缩小时,转换生成的显式高度不会压过响应式布局。
最终效果
Markdown 文章里仍然只需一个简单的 HTML 图片:
<img
alt="Example social card generated by the Subspace Builder"
src="/assets/images/subspace/social-cards/social-cards.png"
/>
转换插件运行后,Eleventy 会输出以下内容,这里摘自开发服务器:
<picture>
<source
type="image/avif"
srcset="
/img/9NWum2aR9G-320.avif 320w,
/img/9NWum2aR9G-640.avif 640w,
/img/9NWum2aR9G-960.avif 960w
"
sizes="(width <= 30em) 100vw, 75vw"
/>
<source
type="image/webp"
srcset="
/img/9NWum2aR9G-320.webp 320w,
/img/9NWum2aR9G-640.webp 640w,
/img/9NWum2aR9G-960.webp 960w
"
sizes="(width <= 30em) 100vw, 75vw"
/>
<img
loading="lazy"
decoding="async"
alt="Example social card generated by the Subspace Builder"
src="/img/9NWum2aR9G-320.jpeg"
width="960"
height="504"
srcset="
/img/9NWum2aR9G-320.jpeg 320w,
/img/9NWum2aR9G-640.jpeg 640w,
/img/9NWum2aR9G-960.jpeg 960w
"
sizes="(width <= 30em) 100vw, 75vw"
/>
</picture>
Eleventy 舍弃了 1280px 的派生图,因为源 PNG 最宽只有 1200px。这正是我们想要的。结合转换插件和 CSS 保护,截图在各种视口下都能保持宽高比,并下载适当大小的文件。
推荐阅读
- MDN 多媒体性能入门:了解
srcset、懒加载和媒体格式的好材料。 - Smashing Magazine:正确使用响应式图片:文章较早,但依然很好地介绍了实用的
<picture>策略。 - Eleventy Img 文档:深入了解自定义文件名格式、
statsOnly,或--serve期间按需转换等覆盖选项。
如果你已有一个 Eleventy 项目,不妨把一个写死的 <img> 换成短代码,重新构建,再检查 HTML。结合上下文查看生成的 <picture> 标记,是理解 srcset 与 sizes 如何配合的最快方式。