CSS 中的响应式图像

Avatar of Chris Coyier
Chris Coyier 发布

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 $200 免费赠送额度!

术语“响应式图像”已成为“HTML 中的响应式图像”的代名词,换句话说,就是 <img>srcsetsizes 属性以及 <picture> 元素。 但是这些功能是如何映射到 CSS 的呢?

CSS 通常并没有真正参与过去几年响应式图像的旅程。 这是有充分理由的:CSS 已经拥有这些工具。 响应式图像从某种意义上说,只是赶上了 CSS 已经可以做的事情。 让我们来看一下。

CSS 中的 srcset

在 HTML 中,srcset 就像这样(取自 Picturefill 网站)

<img srcset="
  examples/images/image-384.jpg 1x, 
  examples/images/image-768.jpg 2x
" alt="…">

一个图像用于 1x 显示,一个更大的图像用于 2x 显示。 如果我们想做同样的事情,只是作为 CSS 中的 background-image,我们可以这样做

.img {
  background-image: url(examples/images/image-384.jpg); 
}
@media 
  (-webkit-min-device-pixel-ratio: 2), 
  (min-resolution: 192dpi) { 
  .img {
    background-image: url(examples/images/image-768.jpg);
  }
}

不过这里确实存在差异。 据我了解,srcset 的规范方式有点像建议。 属性和值提供了有关可用内容的信息,浏览器决定此时此刻最适合的内容。 或者至少,它可以,如果浏览器选择以这种方式实现它。 使用 @media 查询,声明的内容将被执行。

分辨率媒体查询得到了相当好的支持

分辨率媒体查询的浏览器支持情况

还有另一种方法,实际上更接近 srcset 的工作方式,那就是在 CSS 中使用 image-set() 函数

.img {
  background-image: url(examples/images/image-384.jpg);
  background-image: 
    -webkit-image-set(
      url(examples/images/image-384.jpg) 1x,
      url(examples/images/image-768.jpg) 2x,
    );
  background-image: 
    image-set(
      url(examples/images/image-384.jpg) 1x,
      url(examples/images/image-768.jpg) 2x,
    );
}

它比分辨率查询的支持度低一些

image-set() 的浏览器支持情况

它更接近 srcset,不仅因为语法相似,还因为它允许浏览器发挥作用。 根据 (仍在草案阶段)规范

image-set() 函数允许作者忽略大多数这些问题,只需提供图像的多种分辨率,并让 UA 决定在特定情况下最合适哪种分辨率。

在 CSS 中没有完美的 srcset 一对一替代方案,但这种方法非常接近。

CSS 中的 sizes

HTML 中的 sizes 属性与 CSS 直接相关。 实际上,它基本上是说:“这就是我打算在 CSS 中设置图像大小的方式,我只是现在告诉您,因为您可能马上就需要此信息,而不能等到 CSS 先下载。”

示例

<img
  sizes="(min-width: 40em) 80vw, 100vw"
  srcset=" ... "
  alt="…">

假设 CSS 中有类似以下内容

img {
  width: 100%;
}
@media (min-width: 40em) {
  /* Probably some parent element that limits the img width */
  main {
    width: 80%;
  }
}

但是,sizes 本身什么也不做。 您需要将其与 srcset 配合使用,srcset 提供已知的宽度,以便浏览器做出选择。 让我们假设只有两张图像,例如

<img
  sizes="(min-width: 400px) 80vw, 100vw"
  srcset="examples/images/small.jpg 375w,
          examples/images/big.jpg 1500w"
  alt="…">

上面的标记信息提供了浏览器确定最佳图像所需的信息。 浏览器知道 1)它自己的视窗大小和 2)它自己的像素密度。

假设浏览器视窗宽度为 320px 且它是 1x 显示。 它现在还知道将以 100vw 的宽度显示此图像。 因此它必须在提供的两张图像中进行选择。 它会进行一些计算。

375(图像 #1 的大小)/ 320(用于显示图像的像素)= 1.17
1500(图像 #2 的大小)/ 320(用于显示图像的像素)= 4.69

1.17 更接近 1(它是 1x 显示),因此 375w 图像胜出。 它会尽量不低于 1,因此 1.3 会胜过 0.99,据我了解是这样。

现在假设它是 2x 显示。 这会将显示图像所需的像素数量增加一倍,因此计算结果为

375 / 640 = 0.59
1500 / 640 = 2.34

2.34 胜出,因此它将显示 1500w 图像。 如果是 1x 显示,视窗宽度为 1200px 会怎样?

375 /(1200 的 80%)= 0.39
1500 /(1200 的 80%)= 1.56

这里 1500w 图像胜出。

这在 CSS 中写出来有点奇怪和棘手。 如果我们考虑 1x 显示,我们最终会得到以下逻辑…

  • 如果视窗小于 375px,则使用 375w 图像。
  • 如果视窗大于 375px 但小于 400px,则使用 1500w 图像(因为否则我们将放大)。
  • 在 400px 处,图像宽度变为 80vw,因此可以在很短的时间内(在 400px 到 468px 之间)安全地使用 375w 图像()。
  • 超过 468px,则使用 1500w 图像。

我们可以这样写

img {
  background-image: url(small.jpg);
}
/* Only override this if one of the conditions for the 1500w image is met */
@media 
  (min-width: 375px) and (max-width: 400px),
  (min-width: 468px) {
  main {
    background-image: url(large.jpg);
  }
}

在本例中,即使在 300px 这样的非常窄的宽度下,2x 显示仍然需要 600px 才能使质量达到至少 1.0,因此我们也要将此逻辑添加到逻辑中

.img {
  background-image: url(small.jpg);
}
/* Only override this if one of the conditions for the 1500w image is met */
@media 
  (min-width: 375px) and (max-width: 400px),
  (min-width: 468px),
  (-webkit-min-device-pixel-ratio: 2), 
  (min-resolution: 192dpi) {
  .img {
    background-image: url(large.jpg);
  }
}

断点(大小)越多,提供的图像越多,这种复杂性就会呈几何级数增长。 而且它仍然不能完美地匹配响应式图像(在 HTML 中)的功能,因为它不允许浏览器自由裁量(例如,浏览器可能考虑其他因素 [即带宽] 来选择图像)。

CSS 中的 picture

一个示例

<picture>
  <source srcset="extralarge.jpg" media="(min-width: 1000px)">
  <source srcset="large.jpg" media="(min-width: 800px)">
  <img srcset="medium.jpg" alt="…">
</picture>

这种方法可以相当直观地转换为媒体查询。 确切的媒体查询就在那里可以复制

.img {
  background-image: url(medium.jpg);
}
@media (min-width: 800px) {
  .img {
    background-image: url(large.jpg);
  }
}
@media (min-width: 1000px) {
  .img {
    background-image: url(extralarge.jpg);
  }
}

毫不奇怪,这会变得更加复杂,因为 srcset 也可以在 picture 元素中执行其操作

<picture>
  <source srcset="large.jpg, extralarge.jpg 2x" media="(min-width: 800px)">
  <img srcset="small.jpg, medium.jpg 2x" alt="…">
</picture>

这转换为

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

同样,这只是在添加更多图像和条件后等待变得更加复杂。 使用 image-set() 会稍微好一些

.img {
  background-image: url(small.jpg);
  background-image: 
    -webkit-image-set(
      "small.jpg" 1x,
      "medium.jpg" 2x,
    );
  background-image: 
    image-set(
      "small.jpg" 1x,
      "medium.jpg" 2x,
    );
}
@media
  (min-width: 800px) {
  .img {
    background-image: url(large.jpg);
    background-image: 
      -webkit-image-set(
        "large.jpg" 1x,
        "extralarge.jpg" 2x,
      );
    background-image: 
      image-set(
        "large.jpg" 1x,
        "extralarge.jpg" 2x,
      );
  }
}

混合器可以帮助吗?

可能可以? 我很想看到一个 Sass @mixin,它可以接收所有这些参数,考虑图像大小(它自己通过访问磁盘来确定),并输出一些高质量的响应式图像 CSS 代码。 也许甚至可以将分辨率查询和 image-set() 语法结合起来?

我们真的在这样做吗?

我很想知道有多少人正在积极使用 CSS 处理响应式图像。 也许甚至只是在大型断点处交换更大的图像这样的中间方案? 我想知道,因为 picture/srcset 通常是自动化的,所以它的采用率实际上是否比 CSS 中的响应式图像更高?