图片在 Web 中占有非常重要的地位,一方面,一图胜千言,图片作为内容的载体,能够有效传递出信息;另一方面,图片占网页流量消耗的 60%,对网页的大小和加载速度有很大的影响。
随着终端设备日益丰富,人们更多地通过各种尺寸的小屏浏览网页,经常发生图片过大导致网页布局混乱的情况,非常影响用户体验。因此图片需要根据设备类型和尺寸进行自适应调整,才能保证网站在各个终端获得相似的使用体验。
要做到图片自适应宽度,一个简单的方法是,给图片设置 max-width: 100%,这确实非常有作用,图片不会溢出或者页面出现横向滚动条,那还有没有其他需要我们注意的呢?
在使用高清屏幕设备(比如 iPhone X)浏览网站时我们会发现,一些图片看起来比较模糊,而在普通屏幕设备中却没有那么模糊,这是为什么呢?
这里需要用到在上一章中提到的几个概念,为方便起见,我们一起来回顾一下:
物理像素(设备像素)
一个物理像素代表的就是最小的物理显示单元,在同一个设备中,物理像素点的大小是固定的,数量也是固定的。比如 iPhone X 的物理像素是 1125x2436。
逻辑像素(CSS 像素、设备独立像素)
逻辑像素是一个抽象单位,与设备无关,在不同的设备中呈现的大小是一致的,比如逻辑像素为 10x10 的方块,在手机和显示器看上去大小是一样的,这样可以保持阅读体验的一致,同时也方便开发。iPhone X 的逻辑像素是 375x812。
设备像素比(Device Pixel Ratio,DPR)
根据以上两个概念,可以自然地想到,在一个设备中逻辑像素与物理像素的比值是确定的,一个逻辑像素覆盖了多个物理像素,这可以用设备像素比来表示。比如 iPhone X,375x812 的逻辑像素包含了 1125x2436 的物理像素,设备像素比的值就是 3。可以通过下图进一步理解上面三个概念之间的关系。
有了上面的概念,就能简单地解释图片为什么会变模糊。举个例子,分别在普通屏(DRP = 1)和 Retina 屏(DPR = 2)用 CSS 像素为 300x400 的 显示分辨率是 300x400 的图片。
对于普通屏,图像像素和物理像素 1:1,图像不失真。而对于 Retina 屏,图像像素和物理像素 1:4,由于每个图像像素不可分割,物理像素就近取色,从而让图片变得模糊。这种情况需要使用分辨率更高的图片,比如分辨率为 600x800,图像像素和物理像素 1:1,这样就能充分利用 Retina 屏幕的物理像素点,显示出更清晰的图片。
如果在普通屏下也使用分辨率为 600x800 的图片,图像像素和物理像素 4:1,物理像素点只能通过采样,用不足的数量来显示图片。这不仅没有发挥出高清图片的优势,还由于图片的增大而造成了带宽的浪费。
由此可以发现,为了更好地在各种终端设备显示图片,需要考虑设备像素比、图像分辨率、渲染尺寸等因素,如果只是简单地加载图片然后修改样式适应宽度,并不能保证显示效果和加载速度,甚至会大大降低用户的使用体验。因此,需要采用合适的方法对图片进行响应式设计。
媒体查询(media query)
根据前面的分析,图片的响应式设计与渲染尺寸(图片显示大小, 的 CSS 像素)有关,而渲染尺寸通常由设备的视口大小决定,因此就需要一种方法对设备的视口大小进行判断。
CSS 中的媒体查询提供了相关的判断方法,可以根据不同的设备特征应用不同样式。媒体查询支持很多设备特征,由于本章主要介绍响应式图片,实际主要用到其中的视口的宽度和设备方向,如下表所示。
更多关于媒体查询的介绍可以参考 MDN 上的文档《CSS 媒体查询》(https://developer.mozilla.org/zh-CN/docs/Web/Guide/CSS/Media_queries)。
设备特征 | 取值 | 说明 |
min-width | 数值,如 600px | 视口宽度大于 min-width 时判断为真 |
max-width | 数值,如 800px | 视口宽度小于 max-width 时判断为真 |
orientation | portrait | landscape | 当前设备方向,portrait 垂直,landscape 水平 |
对于 min-width 和 max-width 的取值,我们称为断点(Breakpoints)。如何选择断点,主要取决于产品设计本身,没有万能媒体查询的代码。但经过实践,我们也总结了一套比较具有代表性的设备断点。
如果要对细分屏幕大小进行适配,可以查看这篇文章,里面列出了详细的常见设备的媒体查询条件,《media queries for common device breakpoints》(https://responsivedesign.is/develop/browser-feature-support/media-queries-for-common-device-breakpoints/)。
实现响应式图片
前面说了这么多,终于到大家最为关心的部分了。不过先别急,在动手之前,还有一个问题是需要我们去思考的,那就是在根据设备特征选用不同的图片时,是否需要更改比例、裁剪甚至替换整个图片,或者只是仅仅更改图片的分辨率。前者被称为艺术方向(art direction),后者被称为分辨率切换(resolution switching),下面先介绍分辨率切换。
分辨率切换
分辨率切换主要用于优化低分辨率设备的带宽消耗,通常使用在如下场景:开发者提供图片的多个分辨率版本,大屏和高清屏用户获取高分辨率版本,小屏用户获取低分辨率版本。以下是实现分辨率切换的代码例子:
下面具体介绍这两个属性的用法。
srcset
一般形式:
srcset="[url] [num][descriptor], [url] [num][descriptor], …"
指定了一组可选的源,源之间用逗号分隔,每个源由两部分组成:
图片的 url
正整数+宽度描述符 w 或者 正浮点数+像素密度描述符 x,如果该项为空,则默认为 1x
浏览器会通过描述符来选择对应的源,如果使用密度描述符 x,则判断哪一项与设备像素比更接近,例如:
<img srcset="1200x800.jpg 2.5x,
800x600.jpg 2x,
600x400.jpg 1.5x"
src="500x400.jpg">
在 DPR = 2 的 iPhone 6 中会加载 800x600 的图片。
对于宽度描述符 w,由于需要与 sizes 属性一起使用,因此会在下面和 sizes 属性一起介绍。
sizes
一般形式:
sizes="[media-query] [size], [media-query] [size], …, [size]"
指定了一组可选的 size,size 之间用逗号分隔,每个 size 由两部分组成:
媒体查询,但是不能存在于最后一项,因为最后一项用作默认值
图片的 size(CSS 像素),可用 px、em、vw 等单位
当 srcset 属性使用 w 时,sizes 属性才会起作用,浏览器会根据下面的顺序加载图片。
浏览器逐项判断 sizes 属性中的媒体查询条件,当为真时取该项的 size 作为图片的大小信息,如果
的 CSS 样式没有指定大小,则会使用 size 作为
的大小
根据 size 和设备像素比,两者相乘得出图片的分辨率
从 srcset 的宽度描述符中选出不小于所得分辨率中最接近的一项,没有则选最大的一项,取该项作为图片的源
例如:
<img srcset="1200x800.jpg 1200w,
700x500.jpg 700w,
500x400.jpg 500w,
300x200.jpg 300w"
sizes="(max-width: 480px) 350px,
(max-width: 800px) 650px,
1000px"
src="500x400.jpg">
在 DPR = 1 和视口宽度 = 440px 的情况下,根据 sizes 属性获得 size 为 350px,计算 350x1 = 350,在 srcset 中不小于 350 又最接近的是 500w,所以会选择加载 500w 对应的 500x400 图片。
以上就是 srcset 和 sizes 的用法,需要注意的是,以下情况是不正确的:
在同一个 srcset 中混合使用了 x 和 w
在同一个 srcset 有重复的描述符,比如有两项都是 2x
另外值得一提的是,当我们使用 srcset 属性时,实际上是向浏览器提供相关信息,让浏览器作出更好的选择,浏览器可能还会根据用户的偏好、网络条件等因素调整选择。而这个特点,就是接着要介绍的 +srcset 之间的重要区别之一。
如果对 srcset 和 sizes 还想了解更多,可以访问 MDN 的文档《响应式图片》(https://developer.mozilla.org/zh-CN/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images)。
艺术方向
艺术方向听上去可能比较让人费解,但如果用例子来解释就会非常简单。考虑如下情况:
我们提到将图片的 max-width 设置为 100%,图片就会在手机屏幕上压缩到视口的宽度,如果这张图片实际上很大,图片中的内容就会看不清,特别是如果图片主要内容集中在中间,如人像,浏览效果会比较差。遇到这样的情况,最好的方式是在不同的屏幕尺寸下采用不同的图片,让主要内容保持在视口中间,如下图。
