前言
之前就有写过关于 Retina 和 Responsive Image
但写的太烂了, 所以有了后来的 屏幕, 打印, 分辨率, 物理像素, 逻辑像素, Retina, DPI, PPI 是什么? 和这一篇.
参考:
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?
Youtube – srcset and sizes attributes - [ images on the web | part one ], part two, part three
图片响应式的问题
RWD 概念篇 中说了, 网页需要适应不同尺寸的 device, 所以内容要能缩放.
Retina 屏幕 中说了, 高分辨的屏幕会自动 "放大" 图片, 所以图片需要更高清.
要满足不同尺寸和分辨率的设备(屏幕), 图片需要能放大而且不失真.
如果不考虑下载速度和带宽问题, 用一张高清图来 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)
相对简单的场景
比如网页中做一个 person avatar 头像. 通常它在任何屏幕尺寸下一律使用 64x64 px 就可以了. 所谓的 resolution switching = same size.
虽然都是 64px, 但依然要顾虑到屏幕的 device pixel ratio. 电脑 dpr 是 1, 那么 64px 的图就够了, 手机 dpr 是 3 那么就需要 64 x 3 = 192px 的图了.
具体例子:
<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)
比起上面的例子, 相对难一点.
上面的 avatar 是固定 64px, 但不是所以图都是 hardcode 的, 大部分是 depend on viewport 的
比如
device 越大图就越大, 这个就是所谓的 resolution switching: Different sizes.
用 CSS 表达就是 100% 配合 container 的 padding 就是最终图片的 size.
或者依据 viewport 的话, 那就是 100vw - padding * 2 (两边的 padding)
例子:
<img srcset=" https://via.placeholder.com/500x500 500w, https://via.placeholder.com/1000x1000 1000w, https://via.placeholder.com/1500x1500 1500w " sizes="(min-1080px) 1500px, (min- 768px) 1000px, 500px" src="https://via.placeholder.com/1500x1500" /> <!-- The purpose of src is in case it is not supported -->
首先, 一样准备多张不同尺寸的图片, 图片旁边写的 500w, 1000w 表示这张图具体的 width.
sizes 是一个 media query 返回 width, 可以是 px 也可以是 em 或者 vw 但不可以是 % 哦 (因为 browser 在解析 img sizes 时 CSS 是还没有被解析的, 所以不能 depend on parent, depend viewport 就可以).
如果使用 rem 的话, 它是依据 browser 而不是 html override 的哦 (关键就是它不依赖 CSS 就对了)
sizes 的目的就是让 browser 知道图片最终渲染的尺寸, 一般上就是 as per our CSS, (为什么 CSS 都写了, sizes 还得多写一次呢? 还是因为 browser 解释 img sizes 时 CSS 是还没有被解析的, 这教程有提到)
“100vw” is the assumed default of the sizes attribute, sizes 不写的话它是 100vw 的意思
接着, 游览器就会用 match media query 获取到 width 然后去 srcset 里匹配出大小最接近的图.
这个匹配过程还会加上 device pixel ratio. 例子:
sizes="(min-1080px) 1500px, (min- 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- 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-1080px) 1500px, (min- 768px) 1000px, 500px"
if >= 1080 then 1500
else if >= 768 then 1000
else 500
用 CSS 写的话是
h1 { color: red; } @media (min- 768px) { h1 { color: yellow; } } @media (min- 1080px) { h1 { color: blue; } }
注意它的顺序, 是反过来的, 因为 CSS 是往下 override, 但 sizes 并没有那样. sizes 更贴近 JS 的语法.
小总结
1. size 的目的是用来表达图片最终渲染的尺寸.
2. size 不能依赖 CSS, percentage 是不可以用的, 只能靠 vm 配一些 hardcode 我们需要尽可能表达(算出)和 CSS 一样的渲染尺寸.
3. srcset 的目的是提供 avaiable images. 这里是一个 trade-off, 不可能每差 1px 都 provide 另一张图的. 可以依据特定的设备去优化
比如手机常见尺寸是 360, 393, 428 vw, 那么 srcset 就 provide 在这 3 个 vw 下计算出来的 image rendered size 就够了 (同时要顾虑到 device pixel ratio 哦),
如果用户手机是 382px 不在范围内, 那么 browser 会智能的去选择靠近的 393. 这样通常就很好了.
图片和框不同比例的问题
虽然这个和 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 问题:
手机和电脑图片是相同的, 但是比例却不一样.
其重点也不是说比例不一样, 而是不同尺寸的屏幕下, 焦点不一样. (而通常焦点不一样也会导致比例不一样而已)
srcset + sizes 只能解决等比例尺寸的问题, 因为 sizes 只能表达 width. 一个 width 只能指向 srcset 的一张图.
如果有 2 张图 width 一样但是比例不一样它是匹配不了的.
解决方法就是用 picture.
<picture> <source media="(min-1366px)" srcset=" https://via.placeholder.com/500x500 1x, https://via.placeholder.com/1000x1000 2x, https://via.placeholder.com/1500x1500 3x " /> <source media="(min-768px)" srcset=" https://via.placeholder.com/1000x1000 1000w, https://via.placeholder.com/1500x1500 1500w " sizes="(min- 1024px) 1000w, 1500w" /> <img src="https://via.placeholder.com/2000x2000" /> </picture> <!-- The purpose of img is in case it is not supported -->
picture 里面可以有很多的 source, 每一个 source 表示一张不同的图 (比如比例不同). 这样就解决了 sizes 的局限.
简单了解就是 picture 相等于多个 img sizes 和 srcset.
level 1 : resolution switching: same size (same width, same aspect ratio)
level 2 : resolution switching : differect size (different width, same aspect ratio)
level 3 : art direction (different width, different aspect ratio)
每一个 level 的方案都可以解决上一层的问题. 所以 level 3 的方法可以解决所以问题.
但是统一也增加了复杂性. 所以大部分建议用不同的方案 for 不同的需求.
Picture for type support
picture 除了可以做 RWD Image, 还有一个强大的功能就是 type support, 目前市场正转向 webp, 但是还需要兼容旧的 jpeg, 所以网站开发 picture 基本上是一定得用的.
<picture> <source type="image/webp" srcset="illustration.webp"> <source type="image/jpeg" srcset="illustration.jpg"> <source type="image/svg+xml" srcset="illustration.svg"> <img class="my-img" src="illustration.png" alt="A hand-made illustration"> </picture>
1. picture 里的 img element 有 fallback 的作用, 如果 browser 不支持 picture 它会显示 img, 所以把 class, alt 写到 img 上才是正确的做法哦.
2. .jpg 的 MIME types 是 image/jpeg 哦
3. 它匹配是依据顺序的, 如果 webp 不支持那么就 try jpeg. 注: 如果 webp 支持但是图片 404, 它可不会 try jpeg 哦. 它只是判断 type 而已. 不考虑图片是否 avaiable.
提醒: source 使用的是 srcset 而不是 src 哦
src 是专门给 video, audio 用的, 而 srcset 是专门给 picture 用的. 所以哪怕你没有要做 RWD, 只是想做 webp 那依然是用 srcset.
游览器选了那张图 ?
img element 有一个属性叫 currentSrc 可以获取到当前的 src.
用 Chrome Dev Tools hover 到 img 的 src 就可以了
或者 console $0.currentSrc, 在 Chrome console $0 表示当前选择的 element.
总结
图片的 responsive 方式是在不同的情况下加载不同的图. 我们需要提前准备所有的图片 (或者搞一个动态 crop 缓存)
picture source media 匹配 viewport (for art direction problem)
sizes 匹配 viewport (for resolution switching problem), 并返回图片最终渲染时的 width (也要考虑到 cover 和 contain 的场景哦)
srcset 表示所有 available 图片和它们具体的 width.
剩下的就交给游览器自动帮我们匹配就行了.
建议继续看下一篇: HTML & CSS – 实战 RWD Image 响应式图片