CSS – Responsive Image 响应式图片 (完整版)
前言
之前就有写过关于 Retina 和 Responsive Image
但写的太烂了, 所以有了后来的 和这一篇.
参考:
阮一峰 – 响应式图像教程
How to Use HTML5 “picture”, “srcset”, and “sizes” for Responsive Images
What is the art direction problem in Responsive Web Design and how to handle it?
MDN – Responsive images
图片响应式的问题
中说了, 网页需要适应不同尺寸的 device, 所以内容要能缩放.
中说了, 高分辨的屏幕会自动 "放大" 图片, 所以图片需要更高清.
要满足不同尺寸和分辨率的设备(屏幕), 图片需要能放大而且不失真.
如果不考虑下载速度和带宽问题, 用一张高清图来 cover 所有屏幕是可以的. 但真的可以不用考虑吗?
SVG 能解决吗?
参考: 知乎 – 既然矢量图放大缩小都不失真,为什么还要使用位图?
我们知道 SVG 可以无限放大缩小, 这不就很适合做 Responsive Image 吗? 为什么图片要用 JPEG?
这和它们的保存格式有关, SVG 把图像当作 点,线,面,颜色来保存. JPEG 则把每一个像素位置颜色保存.
SVG 是这样表达的, 一个圈, 而 JPEG 的表达是 10个白色, 2 个黑色, 下一行, 5个白色,2个黑色 .... 直到一个圈出现.
也因为这样, SVG 才能缩放自如. 但是 SVG 最大的问题是. 它是画出来的. 你可以用 点, 线, 面 去画画, 但是照相机拍出来的不是画. 它是像素颜色.
所以我们会发现, logo, icon 这些小样的图, 画出来的图, 都是 SVG, 而产品,人像都是 JPEG.
结论: SVG 和 JPEG 的用途是不同的, 也不可能相互替代.
解决思路
既然图片不可能缩放, 那只能是换一张图了. 提前准备不同大小的图片. 判断屏幕的尺寸和分辨率, 智能的去加载最合适的一张.
这就是当前 Responsive Image 的做法,
那要准备多少张呢? 差 1px 也要换一张图?
这是一个 trade-off, 通常会 research 或者看 web analytics. 观察用户的屏幕尺寸.
然后像做 breakpoint 那样. 到一个范围就换一张图.
解决不同分辨率的问题 (Resolution switching: Same size, different resolutions)
<img src="https://via.placeholder.com/1500x1500" srcset=" https://via.placeholder.com/500x500 1x, https://via.placeholder.com/1000x1000 2x, https://via.placeholder.com/1500x1500 3x " />
首先, 准备多张不同尺寸的图片. 上面的例子分别是 500, 1000, 1500 3张图
然后通过 srcset 写上它们对应的 device pixel ratio (不清楚是什么, 请看)
这样表示之后, 游览器就会依据屏幕的分辨率去加载对应的图片了.
解决不同分辨率 + 不同尺寸的问题 (Resolution switching: Different sizes and different resolutions)
<img srcset=" https://via.placeholder.com/500x500 500w, https://via.placeholder.com/1000x1000 1000w, https://via.placeholder.com/1500x1500 1500w " sizes="(min-width:1080px) 1500px, (min-width: 768px) 1000px, 500px" src="https://via.placeholder.com/1500x1500" />
首先, 一样准备多张不同尺寸的图片, 图片旁边写的 500w, 1000w 表示这张图具体的 width.
sizes 是一个 media query 返回 width, 可以是 px 也可以是 em 或者 vw 但不可以是 % 哦.
游览器就会用 match media query 获取到 width 然后去 srcset 里匹配出大小最接近的图.
这个匹配过程还会加上 device pixel ratio. 例子:
sizes="(min-width:1080px) 1500px, (min-width: 768px) 1000px, 500px"
假设 viewport 是 1300px, 它会匹配到 1500px width, 如果是普通电脑的话 (device pixel ratio: 1) 那么就去 srcsec 找 1500w 的图
如果是高清屏幕电脑 (rdevice pixel ratio: 2) 那么就 1500px * 2 = 找 3000w 的图.
如果是手机那么会匹配到 500px, 然后 x 3 = 找 1500w 的图.
device pixel ratio 的影响
一开始接触可能会有点乱, 总之记住:
srcset 的 500w 指的是图片具体的 width (不需要理会 device pixel ratio, 图片多少就写多少)
sizes 的 min-width: 1080px 指的是 viewport 的 width (不需要理会 device pixel ratio)
sizes 的 1500px 指的是这张图片渲染的 width (不需要理会 device pixel ratio)
游览器会很聪明的自己加入 device pixel ratio 概念去找到对的图.
只有在准备图片的时候, 我们需要有 device pixel ratio 概念. 比如手机匹配到的是 500px, 高清手机是 4x, 所以要准备 500,1000,1500,2000w 的图.
sizes media query vs CSS media query
sizes="(min-width:1080px) 1500px, (min-width: 768px) 1000px, 500px"
if >= 1080 then 1500
else if >= 768 then 1000
else 500
用 CSS 写的话是
h1 { color: red; } @media (min-width: 768px) { h1 { color: yellow; } } @media (min-width: 1080px) { h1 { color: blue; } }
注意它的顺序, 是反过来的, 因为 CSS 是往下 override, 但 sizes 并没有那样. sizes 更贴近 JS 的语法.
图片和框不同比例的问题
虽然这个和 Responsive Image 关系不大, 但是要有这个基础才可以讲下面的.
CSS 有 2 个和图片比例有关的属性 object-fit 和 background-size, 值是 cover 或者 contain.
等比例缩小
我们上面提到的 resolution switching 问题, 解决方法就是提前准备多张不同尺寸的图片, 虽然尺寸不同但是比例是相同的.
第 2 张只是小了, 但是图片信息是完整的.
Cover
但如果说第 2 张图的比例不同, 比如是一个正方形
这是 cover 的效果, 可以看到图片左右的信息被删减掉了. Cover 在解决比例问题时, 它是先选一边来压, 然后让另一边丢失图片信息 (保留中央信息)
下面是另一个 cover 的例子. 保留了 horizontal cental 的信息. 丢失了上下.
Contain
contain 的做法是完整保留图片信息, 但是它会让图片上下或者左右留白.
总之当图片比例不符合显示框时, 只能 2 选 1, 要嘛 contain 留白, 要嘛 cover 丢失信息.
Lighthouse 对 cover 和 contain 的要求
做网站需要 take care Google Lighthouse 的分数, 它对图片的其中一个要求就是不要用大图然后 cover.
应该要提前 crop 好一张正确比例的图. Contain 也是同理, 所以在准备图片的时候最好可以依据最终显示刻意 crop 多个版本.
这样才可以确保用户能用尽可能少的带宽和最快的速度下载到图片观看.
解决不同图片比例的问题 (Art direction)
Ari direction 问题:
手机和电脑图片是相同的, 但是比例却不一样, 解决方法就是 crop cover.
srcset + sizes 只适合解决等比例尺寸的问题, 如果是不同比例的话, 更好的做法是用更强大的 picture element.
<picture> <source media="(min-width:1366px)" srcset=" https://via.placeholder.com/500x500 1x, https://via.placeholder.com/1000x1000 2x, https://via.placeholder.com/1500x1500 3x " /> <source media="(min-width:768px)" srcset=" https://via.placeholder.com/1000x1000 1000w, https://via.placeholder.com/1500x1500 1500w " sizes="(min-width: 1024px) 1000w, 1500w" /> <img src="https://via.placeholder.com/2000x2000" /> picture>
picture 里面可以有很多的 source, 每个 source 有个 media 写 media query, 同时它还有 srcset 和 sizes.
它这个功能设计的比较抽象, 所以其实 picture 完全可以替代掉原本的 img srcset sizes, 但是也有点大材小用的问题.
Picture for type support
picture 除了可以做 Responsive Image, 还有一个强大的功能就是 type support, 目前市场正转向 webp, 但是还需要兼容旧的 jpeg, 所以网站开发 picture 基本上是一定得用的.
<picture> <source type="image/webp" srcset="illustration.webp"> <source type="image/svg+xml" srcset="illustration.svg"> <img src="illustration.png" alt="A hand-made illustration"> picture>
总结
图片的 responsive 方式是在不同的情况下加载不同的图. 我们需要提前准备所以的图片 (或者搞一个动态 crop 缓存)
picture source media 匹配 viewport (for art direction problem)
sizes 匹配 viewport (for resolution switching problem), 并返回图片最终渲染时的 width (也要考虑到 cover 和 contain 的场景哦)
srcset 表示所有 available 图片和它们具体的 width.
剩下的就交给游览器自动帮我们匹配就行了.