HTML 响应式图片指南

Avatar of Chris Coyier
Chris Coyier

本指南介绍了响应式图片的 HTML 语法(以及一些 CSS 代码)。我们将介绍 srcset,以及帮助您从图片中获得最佳性能和设计控制的一系列事项。

由 DigitalOcean 提供

DigitalOcean 提供您在任何阶段都需要的云计算服务,以支持您的发展。 立即开始使用免费的 200 美元积分!

本指南介绍了响应式图片的**HTML 语法**(以及一些 CSS 代码)。响应式图片语法是关于根据规则和情况从多个选项中提供**一张图片**。响应式图片有两种形式,它们针对两种不同的用途

如果您唯一的目标是…

提高性能

那么您需要的是…

<img
  srcset=""
  src=""
  alt=""
>

使用响应式图片可以获得**大量**性能提升。 图片权重对页面整体性能有巨大影响,而响应式图片是降低图片权重的最佳措施之一。想象一下,浏览器可以选择 300×300 图片或 600×600 图片。如果浏览器只需要 300×300,**这将有可能节省 4 倍的网络传输字节!**节省通常随着显示分辨率和视窗大小的下降而增加;在最小的屏幕上,一些案例研究表明字节节省率达到了 70-90%。

如果您还需要…

设计控制

那么您需要的是…

<picture>
  <source srcset="" media="">
  <source srcset="" media="">
  <img src="" alt="">
</picture>

响应式图片的另一个完全合法的目标不仅是提供相同图片的不同大小,而是提供不同的图片。例如,根据屏幕尺寸和布局差异对图片进行不同的裁剪。这被称为“艺术指导”。

<picture> 元素还用于备用图片类型以及任何其他类型的媒体查询切换(例如,黑暗模式下的不同图片)。您可以更好地控制浏览器显示的内容。


这里有很多内容要讲,所以让我们介绍一下两种语法、所有相关属性和值,并一路谈谈一些相关主题,例如工具和浏览器。

目录


使用 srcset

<img srcset="" src="" alt=""> 语法用于提供相同图片的不同大小版本。您可以尝试使用此语法提供完全不同的图片,但浏览器会认为 srcset 中的所有内容在视觉上都是相同的,并将以您无法预测的方式选择它们认为最合适的大小。因此,我不建议这样做。

也许最简单可能的响应式图片语法是在图片上添加一个 srcset 属性,并使用 x 描述符对其进行标记,以便在不同像素密度显示器上使用。

<img 
  alt="A baby smiling with a yellow headband."
  src="baby-lowres.jpg"
  srcset="baby-highres.jpg 2x"
>

在这里,我们将默认值(src)设置为图片的“低分辨率”(1×)副本。默认使用最小的/最快的资源通常是明智之举。我们还提供了一个 2× 版本。如果浏览器知道它在更高像素密度显示器上(2x 部分),它将使用该图片。

演示
<img 
  alt="A baby smiling with a yellow headband."
  src="baby-lowres.jpg"
  srcset="
    baby-high-1.jpg 1.5x,
    baby-high-2.jpg 2x,
    baby-high-3.jpg 3x,
    baby-high-4.jpg 4x,
    baby-high-5.jpg 100x
  "
>

您可以根据需要进行任意多个像素密度变体。

虽然这很酷并且有用,但x 描述符只占响应式图片使用率的一小部分。为什么?它们只允许浏览器根据一个因素进行调整:显示像素密度。但是,很多时候,我们的响应式图片位于响应式布局中,并且图片的布局大小会随着视窗大小的缩小和拉伸而缩小和拉伸。在这些情况下,浏览器需要根据两个因素做出决定:屏幕的像素密度和图片的布局大小。这就是 w 描述符和 sizes 属性的用武之地,我们将在下一节中介绍。

使用 srcset / w + sizes

这是好东西。这占网络上响应式图片使用率的 85% 左右。我们仍然在提供相同图片的多个大小版本,只是我们提供了更多信息,以便浏览器可以根据像素密度布局大小进行调整。

<img 
  alt="A baby smiling with a yellow headband."
  srcset="
    baby-s.jpg  300w,
    baby-m.jpg  600w,
    baby-l.jpg  1200w,
    baby-xl.jpg 2000w
  "
  sizes="70vmin"
>

我们仍然提供相同图片的多个副本,并让浏览器选择最合适的副本。但是,我们不是使用像素密度(x)对其进行标记,而是使用资源宽度对其进行标记,使用w 描述符。因此,如果 baby-s.jpg 是 300×450,我们将其标记为 300w

使用像这样带有宽度(w)描述符的 srcset 意味着它需要与 sizes 属性配对,以便浏览器知道图片将在多大空间内显示。没有这些信息,浏览器就无法做出明智的选择。

演示

创建准确的 sizes

创建 sizes 属性可能很棘手。sizes 属性描述了图片在您特定网站的布局中显示的宽度,这意味着它与您的 CSS 密切相关。图片呈现的宽度是依赖布局的,而不是仅仅依赖视窗的!

让我们看一个带有三个断点的相当简单的布局。以下是一个演示此布局的视频

演示

断点在 CSS 中使用媒体查询来表示

body {
  margin: 2rem;
  font: 500 125% system-ui, sans-serif;
}
.page-wrap {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr 200px;
  grid-template-areas:
    "header header"
    "main aside"
    "footer footer";
}

@media (max-width: 700px) {
  .page-wrap {
    grid-template-columns: 100%;
    grid-template-areas:
      "header"
      "main"
      "aside"
      "footer";
  }
}
@media (max-width: 500px) {
  body {
    margin: 0;
  }
}

图片在每个断点处的大小不同。以下是影响图片在最大断点(视窗宽度大于 700 像素时)的布局宽度的所有细节。

图片的宽度与 100vw 相同,减去所有显式大小的 marginpadding、列宽和 gap
  • **在最大尺寸下:**有 9rem 的显式间距,因此图片的宽度为 calc(100vw - 9rem - 200px)。如果该列使用 fr 单位而不是 200px,我们就会陷入困境。
  • **在中等尺寸下:**侧边栏被放到下方,因此要考虑的间距更少。但是,我们可以使用 calc(100vw - 6rem) 来考虑边距和填充。
  • **在最小尺寸下:**主体边距被删除,因此 calc(100vw - 2rem) 就足够了。

呼!说实话,我发现这有点难想,我在创建过程中犯了很多错误。最后,我得到了以下结果

<img 
  ...  
  sizes="
    (max-width: 500px) calc(100vw - 2rem), 
    (max-width: 700px) calc(100vw - 6rem),
    calc(100vw - 9rem - 200px)
  "
/>

一个 sizes 属性,它向浏览器提供了图片在所有三个断点上的宽度,并考虑了布局网格,以及所有最终会影响图片宽度的 gapmarginpadding

现在等等!敲响鼓点!🥁🥁🥁这仍然是错误的。我不明白为什么,因为对我来说,这似乎 100% 描述了 CSS 布局中的情况。但这是错误的,因为 Martin Auswöger 的 RespImageLint 如此表明。在独立演示上运行该工具后,它报告没有问题,除了 sizes 属性对于某些视窗大小是错误的,并且应该是

<img
  ...
  sizes="
    (min-width: 2420px) 2000px, 
    (min-width: 720px) calc(94.76vw - 274px), 
    (min-width: 520px) calc(100vw - 96px), 
    calc(100vw - 32px)
  "
>

我不知道这怎么计算,而且完全无法手动维护,但它是准确的。Martin 的工具会以编程方式调整页面大小,并输出一个sizes属性,该属性描述了图像在各种视窗大小范围内的实际观察宽度。是计算机在进行数学计算,所以它是正确的。因此,如果你想要一个超精确的sizes属性,我建议先在上面放一个错误的属性,运行此工具,然后复制出正确的属性。

要更深入地了解这一切,请查看 Eric Portis 的w描述符和sizes:幕后

sizes更放松

另一个选择是使用马蹄铁和手榴弹方法™的sizes(或者,换句话说,接近即算)。这非常建议

例如,sizes="96vw"表示,“这幅图像在页面上将非常大——几乎占据整个宽度——但边缘周围总是会有一点填充,所以不会完全是。或者sizes="(min-width: 1000px) 33vw, 96vw"表示,“这幅图像在大屏幕上以三栏布局显示,而在其他情况下则接近全宽度”。从实用角度来看,这可能是一个理智的解决方案。

你可能会发现,一些自动响应式图像解决方案,由于不知道你的布局,会进行猜测——例如sizes="(max-width: 1000px) 100vw, 1000px"。这只是在说,“嘿,我们不太了解这个布局,但我们猜测,最坏的情况下,图像的宽度是全宽,希望它永远不会渲染超过 1000 像素”。

抽象sizes

我相信你可以想象,不仅sizes容易出错,而且随着网站布局的变化,它也会变得错误。你可能需要使用模板语言或内容过滤器来抽象它,以便更轻松地更改所有图像的该值。

我基本上是在说,在一个变量中设置sizes值一次,然后在整个网站的许多<img>元素中使用该变量。原生 HTML 不提供这种功能,但任何后端语言都可以;例如,PHP 常量、Rails 配置变量、用于全局状态变量的 React 上下文 API类似 Liquid 的模板语言中的变量 都可以用来抽象sizes

<?php
  // Somewhere global
  $my_sizes = "";
?>

<img
  srcset=""
  src=""
  alt=""
  sizes="<?php echo $my_sizes; ?>"
/>

“浏览器选择”

现在我们已经有了sizes属性,浏览器知道图像将以什么大小(或接近该大小)渲染,并且可以发挥其魔力。也就是说,它可以进行一些数学计算,将屏幕的像素密度和图像将要渲染的大小考虑在内,然后选择最合适的图像大小。

起初,数学计算相当简单。假设你将要显示一个在 1200 像素宽的视窗中、在 2 倍像素密度屏幕上、宽度为 40vw 的图像。完美的图像宽度应该是 960 像素,所以浏览器将寻找它拥有的最接近的东西。浏览器始终会根据视窗和像素密度的状况以及它从sizes中了解到的信息计算出它希望的目标大小,并将该目标大小与它在srcset拥有的内容进行比较,以做出选择。然而,浏览器的选择方式可能会有点奇怪。

如果浏览器选择这样做,它可能会将更多内容考虑进这个等式。例如,它可以考虑用户的当前网络速度,或者用户是否启用了某种“数据节省”偏好。我不确定是否有任何浏览器真正这样做,但如果他们愿意,他们可以这样做,因为这就是规范的编写方式。有些浏览器有时会选择从缓存中提取。如果数学计算表明他们应该使用一个 300 像素的图像,但他们已经在本地缓存中有一个 600 像素的图像,他们就会直接使用它。很聪明。这类事情的空间是srcset/sizes语法的优势。这也是为什么你始终在srcset中使用不同大小的相同图像的原因:你无法知道将选择哪个图像。这是浏览器的选择

这很奇怪。浏览器难道不知道这些东西吗?

你可能会想,“嗯,为什么我必须告诉浏览器图像将以多大尺寸渲染,它难道不知道吗?”嗯,它知道,但只有在它下载了你的 HTML 和 CSS 并完成所有布局之后才知道。sizes属性是关于速度的。它为浏览器提供了足够的信息,让它在看到你的<img>时做出明智的选择。

<img
  data-sizes="auto"
  data-srcset="
    responsive-image1.jpg 300w,
    responsive-image2.jpg 600w,
    responsive-image3.jpg 900w"
  class="lazyload" 
/>

现在你可能会想,“但延迟加载的图像怎么办?”(也就是说,在请求延迟加载的图像时,布局已经完成,浏览器已经知道图像的渲染大小)。嗯,你想得很好!Alexander Farkas 的lazysizes 库会在延迟加载时自动编写sizes属性,并且有一个正在进行的讨论,讨论如何为延迟加载的图像本地执行自动sizes

sizes 可以大于视窗

关于sizes的快速说明。假设你的网站上有一个效果,使图像在被点击时“放大”。也许它会扩展到填满整个视窗,或者也许它会放大更多,以便你可以看到更多细节。在过去,我们可能不得不点击时替换src,以便切换到更高分辨率的版本。但现在,假设更高分辨率的源代码已经包含在srcset中,你只需要将sizes属性更改为一个很大的值,比如200vw300vw,浏览器应该会自动为你下载超高分辨率的源代码。 这里有一篇文章,由 Scott Jehl 撰写,介绍了这种技术。

↩️ 返回顶部


使用<picture>

希望我们已经反复强调<img srcset="" sizes="" alt="">用于服务同一图像的不同尺寸版本。<picture>语法也可以做到这一点,但区别在于,浏览器必须遵守你设置的规则。当你想改变加载图像的分辨率之外的内容以适应用户的状况时,这很有用。这种有意改变图像的行为通常被称为“艺术指导”。

艺术指导

<picture>
  <source 
    srcset="baby-zoomed-out.jpg"
    media="(min-width: 1000px)"
  />
  <source 
    srcset="baby.jpg"
    media="(min-width: 600px)"
  />
  <img 
    src="baby-zoomed-in.jpg" 
    alt="Baby Sleeping"
  />
</picture>

这段代码块是一个例子,展示了“艺术指导”图像的三个阶段可能是什么样子。

  • 在大屏幕上,显示一张缩小的照片。
  • 在中等屏幕上,显示同一张照片,放大了一点。
  • 在小屏幕上,进一步放大。

浏览器必须遵守我们的媒体查询,并且将在我们的精确断点处交换图像。这样,我们就可以绝对确保小屏幕上的任何人都不会看到一张微小的、缩小的图像,这可能不像放大版本那样具有相同的效果。

这是一个演示,使用 Pug 来抽象出<picture>的一些重复性质。

艺术指导可以做的事情远不止裁剪

虽然像这样裁剪和缩放是最常见的艺术指导形式,但你可以用它做很多事情。例如,你可以

真的,天空是极限。

结合 sourcesrcset

因为 <source> 也使用 srcset 语法,所以它们可以组合在一起。这意味着即使使用 <source> 交换视觉上不同的图像,您仍然可以获得 srcset 的性能优势。不过代码会变得相当冗长!

<picture>
  <source 
    srcset="
      baby-zoomed-out-2x.jpg 2x,
      baby-zoomed-out.jpg
    "
    media="(min-width: 1000px)"
  />
  <source 
    srcset="
      baby-2x.jpg 2x,
      baby.jpg
    "
    media="(min-width: 600px)"
  />
  <img 
    srcset="
      baby-zoomed-out-2x.jpg 2x
    "
    src="baby-zoomed-out.jpg"
    alt="Baby Sleeping"
  />
</picture>

您创建的变体越多,每个变体创建的调整大小版本越多,此代码就必须越冗长。

现代图像格式的回退

<picture> 元素非常适合处理“回退”。也就是说,使用最前沿格式的图像,并非所有浏览器都能处理这些图像,因此可以为无法加载首选的精美格式的浏览器提供备用格式。例如,假设您想使用 WebP 格式的图像。它是一个非常棒的图像格式,通常是性能最好的选择,并且在所有支持 <picture> 元素的地方都得到支持,除了 Safari。您可以自行处理这种情况,例如

<picture>
  <source srcset="party.webp">
  <img src="party.jpg" alt="A huge party with cakes.">
</picture>

这成功地向支持它的浏览器提供 WebP 图像,并回退到 JPEG 图像,这绝对被所有浏览器支持。

以下是一个相同大小的照片(我的照片)示例,其中 WebP 版本的大小约为 JPEG 版本的 10%(!!!)。

如何创建 WebP 图像?好吧,它比你想要的麻烦得多,这是肯定的。有 在线转换器命令行工具,以及一些现代设计软件(如 Sketch)可以帮助您导出这种格式。我更喜欢使用自动将图像发送到请求浏览器的最佳格式的图像托管 CDN 服务,这使得所有这一切都变得不必要(因为您可以直接使用 img/srcset)。

WebP 不是唯一这样的玩家。Safari 不支持 WebP,但确实支持一种名为 JPG 2000 的格式,它比 JPEG 有一些优势。Internet Explorer 11 碰巧支持一种名为 JPEG-XR 的图像格式,它具有不同的优势。因此,要命中所有三个,它可能看起来像这样

<picture>
  <source srcset="/images/cereal-box.webp" type="image/webp" />
  <source srcset="/images/cereal-box.jp2" type="image/jp2" />
  <img src="/images/cereal-box.jxr" type="image/vnd.ms-photo" />
</picture>

此语法(借自 Josh Comeau 的博客文章)一次性支持所有三种“下一代”图像格式。IE 11 不支持 <picture> 语法,但这没关系,因为它会得到它理解的 JPEG-XR 格式的 <img> 回退。

Estelle Weyl 也在 2016 年的一篇关于 图像优化 的博客文章中介绍了这个想法。

↩️ 返回顶部


从哪里获得不同大小的图像?

您可以自己制作。哎呀,即使是我 Mac 上的免费 Preview 应用程序也可以调整图像大小并“另存为”。

Mac Preview 应用程序调整图像大小,这实际上是任何图像编辑应用程序(包括 Photoshop、Affinity Designer、Acorn 等)都可以做到的。此外,它们通常 通过一次性导出变体来提供帮助

但这项工作。更有可能的是,这些图像变体的创建是通过某种方式自动化的(参见下面的部分),或者您使用的是一种服务,该服务允许您通过操作图像 URL 来创建变体。这是任何图像托管/图像 CDN 服务的超常见功能。列举几个

这些服务不仅提供动态图像调整大小,而且通常还提供其他功能,例如裁剪、过滤、添加文本以及各种有用的功能,更不用说从 CDN 高效地提供资产并在下一代格式中自动提供资产了。我认为,这使得它们成为几乎任何网站的强大选择。

以下是 Glen Maddern 在一个非常棒的屏幕录像中谈论图像 CDN 有多么有用

设计软件越来越意识到我们经常需要图像的多个副本。例如,Figma 的导出界面非常不错,可以导出任何选定内容。它允许一次导出多个内容(以不同的尺寸和格式),并记住您上次导出时所做的事情。

在 Figma 中导出

自动响应式图像

响应式图像的语法非常复杂,以至于手动操作通常是不可能的。我强烈建议尽可能地自动化和抽象这一过程。幸运的是,许多帮助您构建网站的工具都知道这一点,并包含对它的某种支持。我认为这很棒,因为这就是软件应该为我们做的事情,尤其是在它是完全程序化的并且可以通过代码比通过人类更好地完成的事情时。以下是一些示例……

  • Cloudinary 有这个 响应式断点 工具,包括用于生成完美断点的 API。
  • WordPress 会生成多个版本的图像,并在响应式图像语法中输出 默认情况下
  • Gatsby 有一堆用于转换和在您的网站上实现图像的插件。您最终会使用 gatsby-image 来实现它们,它是一个用于实现响应式图像和其他图像加载优化的完整奇特东西。说到 React,它有像 “几乎完美的 React 图像组件” 这样的组件抽象,它也会做很酷的事情。
  • Nicolas Hoizey 的 Images Responsiver Node 模块(以及它的 Eleventy 插件)为您做出了很多明智的标记选择,并与可以处理动态调整大小位的 CDN 配合良好。
  • 这些只是一些示例!您可以采取任何措施来使此过程变得更轻松或自动化,这都是值得的。
以下是我检查 WordPress 博客文章中的图像,并看到了一个强大的 srcset,其中包含大量预生成的尺寸选项和一个针对此主题定制的 sizes 属性。
gatsby-image 的登录页面,解释了它可以做的所有其他图像加载内容。

我相信还有很多其他 CMS 和软件产品可以帮助自动化创建响应式图像语法的复杂性。虽然我喜欢所有这些语法都存在,但我发现它们完全太繁琐,无法手动编写。尽管如此,我认为了解所有这些语法是值得的,这样我们就可以构建自己的抽象,或者检查我们正在使用的抽象,以确保它们在正确地执行操作。

  • CSS 中的 object-fit 属性控制图像在其自身框中的行为方式。例如,如果将图像尺寸更改为与其自然纵横比不同的尺寸,则图像通常会“挤压”,但 object-fit 可以用来裁剪或包含它。
  • CSS 中的 object-position 属性允许您在图像框内调整图像位置。

使用 CSS 背景图像的响应式图像怎么办?

我们之前已经详细介绍过这一点。诀窍是使用 @media 查询更改 background-image 源。例如

.img {
  background-image: url(small.jpg);
}
@media 
  (min-width: 468px),
  (-webkit-min-device-pixel-ratio: 2), 
  (min-resolution: 192dpi) {
  .img {
    background-image: url(large.jpg);
  }
}

使用此 CSS 语法,根据浏览器条件,浏览器只会下载两个图像中的一个,这实现了与 HTML 中的响应式图像语法相同的性能目标。如果这有帮助,请将上面的内容视为 <picture> 语法的 CSS 等效项:浏览器必须遵循您的规则并显示匹配的内容。

如果您想让浏览器选择最佳选项,就像srcset/sizes一样,但在 CSS 中,最终的解决方案将是 image-set() 函数。不过,image-set() 目前有两个问题。

  • 它还没有得到普遍支持。Safari 的实现领先于其他浏览器,但 image-set() 在 Chrome 中已经使用了八年的前缀,而 Firefox 根本不支持。
  • 即使 规范本身 似乎也落后于时代。例如,它只支持 x 描述符(没有 w尚未)。

现在最好还是使用媒体查询。

您需要填充吗?

我对现在就填充这些东西有点无感。不过,有一个很棒的填充库叫做 Picturefill,它可以为您提供完整的 IE 9-11 支持,如果您需要的话。不过,请记住,如果您的代码中有一个 <img src="" alt="">,这些东西都不会导致完全不显示任何图像。如果您做出(相当安全的)假设 IE 11 在低像素密度的桌面显示器上运行,您可以默认使您的图像源反映这一点,并从那里开始构建。

其他重要的图像注意事项

  • 优化质量:自适应图像的意义在于加载尽可能小的、最有影响力的资源。没有有效压缩图像,就无法实现这一点。您要为每张图像找到一个“最佳点”,在看起来不错和文件大小轻盈之间取得平衡。我喜欢让图像托管服务帮我解决这个问题,但 Etsy 有 一篇很棒的文章,介绍了他们使用自己构建的基础设施所取得的成就。
  • 从 CDN 提供服务:说到图像托管服务,速度有很多形式。速度的一个重要因素是地理位置靠近用户的快速服务器。
  • 缓存:还有什么比减少网络数据加载更好的方法呢?根本不加载数据!这就是 HTTP 缓存 的作用。使用 Cache-Control 标头,您可以告诉浏览器保存图像,这样如果再次需要同一图像,浏览器就不必通过网络获取它,这对重复查看来说是一个巨大的性能提升。
  • 延迟加载:这是另一种完全避免加载图像的方法。延迟加载 意味着等到图像进入视窗或靠近视窗时才下载它。因此,例如,如果用户从未滚动到页面底部,页面最底部的图像就不会加载。

其他好的资源

(我还没有在文章中链接它们!)

浏览器支持

这是针对 srcset/sizes 的,但 <picture> 也是一样的。

这些浏览器支持数据来自 Caniuse,其中包含更多详细信息。数字表示该浏览器从该版本开始支持该功能。

桌面

ChromeFirefoxIEEdgeSafari
3838不支持16部分支持

移动设备/平板电脑

Android ChromeAndroid FirefoxAndroidiOS Safari
12712712718.0