响应式Web之图像

响应式设计中,图像一直是个难点。一方面,我们想尽可能地使用美丽的图像来展示内容;另一方面,考虑到移动设备的网络速率与处理能力,我们需要单独准备图片,并让其与已有的图片针对不同大小的视口(屏幕分辨率、设备像素比等)做出恰当地响应。有的时候,基于美术设计,我们还会手动裁剪出各种不成比例的图片。

像素/分辨率/设备像素密度/设备像素比

像素是什么?

在计算机屏幕上,像素是一个彩色(或灰度)的物理点(屏幕可以显示的最小的点)。

注意: 严格来说,我们应该称其为 设备像素(Device Pixel)或物理像素(Physical Pixel),因为还有设备无关像素。

通常而言,屏幕像素的数量决定了其分辨率的大小。例如,1920x1080 分辨率的显示器是 1920像素宽乘以 1080像素高。

分辨率,屏幕水平方向上的像素数乘以屏幕竖直方向上的像素数。

所谓设备像素密度,跟我们常见的手机参数 PPI(Pixel Per Inch) 是一回事儿,其直译过来是“像素每英寸”,但考虑到它的含义,我们可以称呼它为像素密度(Pixel Density)。像素密度的计算方式,就是用屏幕对角线上的像素值除以屏幕对角线的长度(单位:英寸)。例如,iPhone5 的屏幕对角线的像素值为 1303,其对角线长度为 4 英寸,所以其像素密度为 1303/4=326PPI。

设备像素密度(PPI),即设备每英寸所含有的像素数量。

截至到目前为止,我们所说的“像素”均指的是设备像素,也就物理设备自身实际所拥有的像素。

而通常我们在 CSS 中书写的单位 px,跟设备像素并没有多大关系。这种像素通常被称为“设备无关像素”,Web 开发中常用的 CSS 像素就是设备无关像素的一种。设备无关像素与设备像素之间的存在一种“设备像素比”的关系。

设备像素比(DPR)就是设备像素与设备无关像素(在Web中,通常就是 CSS 像素)的比值。

如果想知道当前设备的 DPR 大小,我们可以在浏览器控制台打印 window.devicePixelRatio。一般来说,window.devicePixelRatio 值默认都是 1,除非网页被强行进行缩放。

最简单的响应式图像

对于已有工程中图像的处理,我们所能想到的最简单的响应式开发方案就是对其宽度作自适应,而考虑到图像自身宽度可能大于容器的情况下,我们应该使用 max-width 属性:

1
2
3
4
.img {
max-width: 100%;
height: auto;
}

以上,可以称得上是最简单的响应图片处理方案了。

srcset属性详解

<img> 标签具有名为 srcset 的属性,支持针对不同设备像素比/视口宽度设置不同的图片源。

响应设备像素比

1
<img src="small.jpg" alt="image" srcset="large.jpg 2x, small.jpg 1x">

上面代码中 2x1x 中的后缀 x 表示设备像素比,所以 2x 的含义即是设备像素比大小为 2,1x 同理。

如果浏览器支持 srcset 属性,那么它就会遵循 srcset 的设定从而以设备像素比为准则来选择如何加载图片。例如,对于上述代码,在设备像素比为 2 的情况下,浏览器会加载图像 large.jpg

注意,即使设置了 srcset 属性,我们还应该设置 src 属性,以保证代码在不支持 srcset 属性的浏览器上能够做到优雅降级。

响应视口宽度

视口宽度与设备像素比有很大不同,设备像素比描述的是精确值,而视口宽度描述的则是边界值,即视口宽度在某个宽度范围内适用某一张图片。

所以这里有个问题,针对视口宽度的响应,srcset 中设定的值表示的是最小宽度还是最大宽度???

考虑如下代码:

1
2
3
4
5
<img src="small.jpg" alt="image" srcset="medium.jpg 600w, large.jpg 800w">
<!-- 或者 -->
<img src="large.jpg" lat="image" srcset="medium.jpg 800w, small.jpg 600w">

同响应设备像素比类似,上述代码中 800w600w 的后缀 w 表示视口宽度。

上述代码,前者秉承移动优先的理念,默认加载 small.jpg,视口宽度大于 600px 时加载 medium.jpg,大于 800px 时则加载 large.jpg
相反地,后者遵循的是桌面优先理念,默认加载 large.jpg,视口宽度小于 800px 时加载 medium.jpg,小于 600px 时则加载 small.jpg

就实际情况来看,大部分浏览器遵循的是移动优先,也就是说,只有视口宽度超过某一设定值时,才加载更大的图片。

实际运行时,浏览器永远对页面的渲染布局拥有最高指挥控制权,所以有时 srcset 对视口宽度的响应实际表现跟理论有出入:

  1. 如果浏览器支持 srcset 属性,那么 src 属性中的图片永远不会加载
  2. 当浏览器已经加载了某一张图片之后,再对浏览器窗口进行缩放,即使此时视口宽度匹配了 srcset 语法规则中的某条设定,浏览器也不会再加载图片了
  3. Chrome 会充分利用缓存,如果缓存中有上次访问留下的高清图片,那么即使此时是从窄屏进行访问,也回加载高清图片

神奇的sizes语法

除了 srcset 属性,<img> 标签还有一个更神奇的 sizes 属性,它的语法如下:

1
size="[meida query] [length], [media query] [length] ... [media query] [lenghth]"

sizes 属性值由一系列由逗号隔开的描述图片宽度的特征表达式组成,每一组包含两部分:

  • [media query] 表示匹配的查询条件
  • [length] 表示该查询条件下图片所占用的宽度

其中,最后一个特征表达式可以只描述图片大小,表示在默认查询条件下的图片占用宽度。

下面是示例:

1
<img src="" alt="" sizes="(max-width: 640px) 100vw, (max-width: 960px) 50vw, calc(100vw / 3)"

上述代码意味着:

  1. 视口宽度小于 640px 时,图片宽度为视口宽度的 100%
  2. 视口宽度大于 640px 且小于 960px 时,图片宽度为视口宽度的 50%
  3. 视口宽度大于 960px 时,图片宽度为视口宽度的 1/3

sizes属性结合srcset属性

我们可以将 <img> 标签的 srcset 属性和 sizes 属性结合起来使用。

1
<img src="" alt="" sizes="(max-width: 640px) 100vw, (max-width: 960px) 50vw, calc(100vw / 3)" srcset="large.jpg 1024w, medium.jpg 640w, small.jpg 320w">

这里有一点需要注意,当 sizes 属性存在时,srcset 中每一张图片名称后面跟随的不再是设备条件,而是图片的宽度,且仍以 w 为后缀表示像素宽度。

对于上述代码,浏览器首先会根据 sizes 语法规则设定的媒体查询条件判断出图片应该有的宽度,然后从 srcset 设定的图片源中加载最符合的。

元素让我们忘了

尽管传统的 <img> 得益于 srcsetsizes 属性支持可以作为解决响应式图像的方案,但仍显得力不从心,有时候浏览器奇怪的表现也让我们无所奈何。

HTML5 新引入的 <picture> 元素及 <source> 元素让我们看到了响应式图像解决方案的曙光。

<source> 元素支持媒体查询,也支持 srcset 属性,srcset 属性的使用方法与 <img> 标签中一致。

1
2
3
4
5
6
<picture>
<source media="(max-width: 640px)" srcset="small.jpg 320w" />
<source media="(max-width: 960px)" srcset="medium.jpg 640w" />
<source srcset="large.jpg 1024w" />
<img src="small.jpg" alt="">
</picture>

注意: 只有 <img> 存在,<picture> 元素才能显现图片。毕竟,<source> 元素只是为 <picture> 提供图片数据源,并没有可视化的能力。

依据 <source> 的媒体查询信息选择来源是非常精确的,与样式表中的媒体查询一致,不会像 <img>srcsetsizes 属性那样由浏览器自信裁决。

下面展示一个颇为复杂的示例:

1
2
3
4
5
6
<picture>
<source media="(max-width: 640px)" srcset="small2.jpg 2x, small.jpg 1x" sizes="calc(100vw / 3)" />
<source media="(max-width: 960px)" srcset="medium2.jpg 2x, medium.jpg 1x" />
<source srcset="large2.jpg 2x, large.jpg 1x" />
<img src="small.jpg" alt="" >
</picture>

对于上述代码,浏览器的解析方案如下:

  1. 执行 <source> 中设定的媒体查询,选择一个图片源
  2. 一旦浏览器选择了某个 <source> 作为图片源之后,该 <source>sizes 属性立即生效,作为它所在 <picture> 的宽度
  3. <picture> 宽度确定之后,浏览器会根据当前设备情况、图片尺寸情况,从 srcset 中选择适合的图片

元素的弊端

拥有 srcsetsizes 属性的 <img> 元素实际上是将图片的选择权全权交给了浏览器,而 <picture> 元素选择 <source> 则是要依赖人工设计的媒体查询的执行结果,根据设备被动选择。

##

拯救世界的Client Hints

HTTP Client Hints 是一种比较新的技术,利用这项技术,HTTP 客户端(通常可以认为是浏览器)能够主动将一些特性告诉服务端,以便服务端更有针对性地输出内容。

其实之前浏览器已经将很多自身特性放在 HTTP 请求中,例如下面这些头部字段:

  • User-Agent:提供浏览器类型及版本、操作系统及版本、浏览器内核等信息;
  • Accept:表明浏览器支持哪些 MIME type(例如 Chrome 通过 Accept 表明自己支持 image/webp 图片格式);
  • Accept-Encoding:表明本浏览器支持哪些内容编码方式(例如:gzip、deflate、sdch);
  • Accept-Language:表明本浏览器支持那些语言;

通过以上这些头部字段,我们已经可以针对不同客户端输出不同内容。但是有一些浏览器特性,我们无法直接获取,如屏幕分辨率、设备像素比(devicePixelRatio)、用户带宽等。而在移动 Web 中,为了尽可能节省用户流量,需要输出尺寸最合适的图片资源。

为了解决这种问题,HTPP Client Hints 技术为 HTTP 请求头规定了几个新的字段:

  • DPR:设备像素比
  • Viewport-Width:视口宽度
  • Width:图片的实际宽度
  • Downlink:客户端最大下载速率
  • Save-data:布尔值,表示是否应采取额外措施来减少有效负载

为了使用这项最新的功能,以获得更好的响应式图像体验,我们可以通过配置 HTTP 请求头和 meta 标签两种方式开启:

  1. Accept-CH: DPR, Width, Viewport-Width
  2. <meta http-equiv="Accept-CH" content="DPR, Width, Viewport-Width">