响应式设计中,图像一直是个难点。一方面,我们想尽可能地使用美丽的图像来展示内容;另一方面,考虑到移动设备的网络速率与处理能力,我们需要单独准备图片,并让其与已有的图片针对不同大小的视口(屏幕分辨率、设备像素比等)做出恰当地响应。有的时候,基于美术设计,我们还会手动裁剪出各种不成比例的图片。
像素/分辨率/设备像素密度/设备像素比
像素是什么?
在计算机屏幕上,像素是一个彩色(或灰度)的物理点(屏幕可以显示的最小的点)。
注意: 严格来说,我们应该称其为 设备像素
(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
属性:
|
|
以上,可以称得上是最简单的响应图片处理方案了。
srcset属性详解
<img>
标签具有名为 srcset
的属性,支持针对不同设备像素比/视口宽度设置不同的图片源。
响应设备像素比
|
|
上面代码中 2x
和 1x
中的后缀 x
表示设备像素比,所以 2x
的含义即是设备像素比大小为 2,1x
同理。
如果浏览器支持 srcset
属性,那么它就会遵循 srcset
的设定从而以设备像素比为准则来选择如何加载图片。例如,对于上述代码,在设备像素比为 2 的情况下,浏览器会加载图像 large.jpg
。
注意,即使设置了 srcset
属性,我们还应该设置 src
属性,以保证代码在不支持 srcset
属性的浏览器上能够做到优雅降级。
响应视口宽度
视口宽度与设备像素比有很大不同,设备像素比描述的是精确值,而视口宽度描述的则是边界值,即视口宽度在某个宽度范围内适用某一张图片。
所以这里有个问题,针对视口宽度的响应,srcset
中设定的值表示的是最小宽度还是最大宽度???
考虑如下代码:
|
|
同响应设备像素比类似,上述代码中 800w
和 600w
的后缀 w
表示视口宽度。
上述代码,前者秉承移动优先的理念,默认加载 small.jpg
,视口宽度大于 600px 时加载 medium.jpg
,大于 800px 时则加载 large.jpg
。
相反地,后者遵循的是桌面优先理念,默认加载 large.jpg
,视口宽度小于 800px 时加载 medium.jpg
,小于 600px 时则加载 small.jpg
。
就实际情况来看,大部分浏览器遵循的是移动优先,也就是说,只有视口宽度超过某一设定值时,才加载更大的图片。
坑
实际运行时,浏览器永远对页面的渲染布局拥有最高指挥控制权,所以有时 srcset
对视口宽度的响应实际表现跟理论有出入:
- 如果浏览器支持
srcset
属性,那么src
属性中的图片永远不会加载 - 当浏览器已经加载了某一张图片之后,再对浏览器窗口进行缩放,即使此时视口宽度匹配了
srcset
语法规则中的某条设定,浏览器也不会再加载图片了 - Chrome 会充分利用缓存,如果缓存中有上次访问留下的高清图片,那么即使此时是从窄屏进行访问,也回加载高清图片
神奇的sizes语法
除了 srcset
属性,<img>
标签还有一个更神奇的 sizes
属性,它的语法如下:
|
|
sizes
属性值由一系列由逗号隔开的描述图片宽度的特征表达式组成,每一组包含两部分:
- [media query] 表示匹配的查询条件
- [length] 表示该查询条件下图片所占用的宽度
其中,最后一个特征表达式可以只描述图片大小,表示在默认查询条件下的图片占用宽度。
下面是示例:
|
|
上述代码意味着:
- 视口宽度小于 640px 时,图片宽度为视口宽度的 100%
- 视口宽度大于 640px 且小于 960px 时,图片宽度为视口宽度的 50%
- 视口宽度大于 960px 时,图片宽度为视口宽度的 1/3
sizes属性结合srcset属性
我们可以将 <img>
标签的 srcset
属性和 sizes
属性结合起来使用。
|
|
这里有一点需要注意,当 sizes
属性存在时,srcset
中每一张图片名称后面跟随的不再是设备条件,而是图片的宽度,且仍以 w
为后缀表示像素宽度。
对于上述代码,浏览器首先会根据 sizes
语法规则设定的媒体查询条件判断出图片应该有的宽度,然后从 srcset
设定的图片源中加载最符合的。
元素让我们忘了![]()
尽管传统的 <img>
得益于 srcset
、sizes
属性支持可以作为解决响应式图像的方案,但仍显得力不从心,有时候浏览器奇怪的表现也让我们无所奈何。
HTML5 新引入的 <picture>
元素及 <source>
元素让我们看到了响应式图像解决方案的曙光。
<source>
元素支持媒体查询,也支持 srcset
属性,srcset
属性的使用方法与 <img>
标签中一致。
|
|
注意: 只有 <img>
存在,<picture>
元素才能显现图片。毕竟,<source>
元素只是为 <picture>
提供图片数据源,并没有可视化的能力。
依据 <source>
的媒体查询信息选择来源是非常精确的,与样式表中的媒体查询一致,不会像 <img>
的 srcset
和 sizes
属性那样由浏览器自信裁决。
下面展示一个颇为复杂的示例:
|
|
对于上述代码,浏览器的解析方案如下:
- 执行
<source>
中设定的媒体查询,选择一个图片源 - 一旦浏览器选择了某个
<source>
作为图片源之后,该<source>
的sizes
属性立即生效,作为它所在<picture>
的宽度 <picture>
宽度确定之后,浏览器会根据当前设备情况、图片尺寸情况,从srcset
中选择适合的图片
元素的弊端
拥有 srcset
和 sizes
属性的 <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 标签两种方式开启:
Accept-CH: DPR, Width, Viewport-Width
<meta http-equiv="Accept-CH" content="DPR, Width, Viewport-Width">