本文由Spy根据Eric的《Srcset and sizes》一文所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:http://ericportis.com/posts/2014/srcset-sizes。
——作者:Eric
——译者:Spy
媒体查询有何不对?
从February 23rd, 1993到May 25th, 2010制作一个网页,图片是件容易的事情!简单点说:
- 网页是固定布局
- 每个人都可以根据图片在屏幕上大小测量出他们的大小
- 流行的Photoshop
- 点击
文件>存储为Web所用格式...
可以保存你需要的图片大小 - 在页面中使用
<img>
标签 - 可以庆祝你成功完成在你的页面添加图片
虽然有些聪明的人跌跌撞撞地走出现有的视野,但谈到这种固有的方法存在的问题,已围绕网页设计师近二十年之久。
但时代在变,他们也跟着在变。
四年前,ETHAN MARCOTTE发布了一篇文章(《Responsive Web Design》)之后的第13天,Steve Jobs发布了iPhone,突然有了流体和视网膜(Retina)图像一说;也突然有了相关争论。
我们面对实现流体图片又名视网膜又名响应式图片的第一反应就是使用制作响应式布局的工具:媒体查询!
浏览器无法知道它加载的网页是什么,但他可以意识到他们的渲染环境,比如说视窗大小,用户屏幕的分辨率等诸如此类的事情。媒体查询的思路是这样的:让Web开发人员根据具体环境做具体事情。如视窗像素超过上千,则显示左侧边栏,否则侧边栏将放在主内容底下。如果用户的屏幕支持视网膜,则使用一个大图像,否则使用一个较小的图像。
不幸的是,当涉及到响应式图像,在许多情况下,在实践中使用媒体查询选择同一个图片源真是件可怕的事情。
这是值得花时间探讨的问题。媒体查询基于响应式图片源是件可怕的事情,虽然很多设计师会根据视窗大小这个变量改变页面布局,提供响应式图像。但我们真正关心的只是三个变量:
- 布局中渲染图像是使用像素
- 屏幕密度
- 处理尺寸时也要处理各种文件大小
总之,媒体查询就是一种解决问题的方法。
一旦我们了解了这三点,要有一个解决方案就不是难事。在一组的资源中,挑最小的一个,其尺寸仍然大于渲染尺寸(rendered size
) × 屏幕密度(screen density
)。
但是,不幸的是!渲染尺寸(rendered size
)是一个棘手的事。Web开发人员可以不知道他。弹性图像(Flexible image)是伸缩性图像。在响应式布局当中图像的渲染尺寸是任何可能都有的。或许令人惊讶的是,浏览器开始加载图像并不知道其渲染尺寸——渲染尺寸都依赖于CSS。
令人高兴的是,媒体查询可以让我们根据断点重新渲染图像尺寸大小。
- 视窗大小
- 根据视窗大小调整图像渲染尺寸
这要求根据视窗大小和屏幕密度,这之后可以计算出需要尺寸。
要怎么计算呢?我们来看一个示例。
(需要注意的是,如何让计算更为简单,这个例子存在的理由就是告诉你,媒体查询计算过于频繁,容易出错。如果你也这么的认为,你可自动的跳到文章第二部分)。
比如说,你有三个版本的图像:
- large.jpg (1024 x 768)
- medium.jpg (640 x 480)
- small.jpg (320 x 240)
你要支持1x和2x的设备像素比(device-pixel-ratio
)。
如何创建你的媒体查询,我们从顶部开始。
当large.jpg
图片必须得加载的时候,medium.jpg
和small.jpg
都太小。也就是说,我只希望加载large.jpg
:
渲染宽度(rendered width) × 屏幕密度(screen density) > 下一个最小文件的宽度
在我们的布局示例中,渲染宽度rendered width
只是视窗的一个百分比,其结果是:
渲染宽度(rendered width) = 图像相对于视窗的宽度 × 视窗宽度(viewport width)
下一最小文件是medium.jpg
,所以:
下一个最小文件的宽度(width) = 640px
把他们放在一起,我们得到下面的不等式:
图像相对于视窗的宽度 × 视窗宽度(viewport width) × 屏幕密度(screen density) > 640px
如果我们重新调整视窗的宽度:
视窗宽度(viewport width) > 640px ÷ ( 图像相对于视窗宽度 × 屏幕密度(screen density) )
为了构建媒体查询,我们解决了视窗宽度(viewport width
),尽可能的为图像提供了一个相对于视窗的宽度和屏幕密度(screen density
)。
图像相对于视窗的宽度可以是两个值中的一个:100%之前(假设我们的断点是36em)或33%之后。
至于屏幕的密度(screen density
),这可能有很多事情要做。但我们说过,我们只需要支持1x和2x的设备分辨率(device-pixel-ratios
)。
两种相对于视窗宽度的图像 X 两种可能的屏幕密度 = 四种情况,我们就需要考虑,我们是不是在一处就可以解决。
1X,断点之前
我们的断点是在36em
,按照相关定义,我们知道
视窗宽度(viewport width) < 36em
插入图像相对于视窗宽度等于100%,屏幕密度等于1,我们就可以得出:
视窗宽度(viewport width) > 640px ÷ ( 100% × 1x ) = 640px = 40em
结合两者,我们可能得到是:
36em > 视窗宽度(viewport width) > 40em
因此我们可以抛出解决方案,在1x范围内的单列布局,我们永远需要large.jpg
。
2X,断点之前
又一次:
视窗宽度(viewport width) < 36em
现在我们插入的屏幕密度是2x:
视窗宽度(viewport width) > 640px ÷ ( 100% × 2x ) = 320px = 20em
结合两者,我们得到的是:
36em > 视窗宽度(viewport width) > 20em
因此,当在屏幕密度为2x时,而且视窗宽度在这个范围之内,我们需要加载large.jpg
。
1X,断点之后
视窗比断点更宽:
视窗宽度(viewport width) > 36em
而且我们在一屏是看到的是三栏布局:
视窗宽度(viewport width) > 640px ÷ (33.3vw × 1x) = 1920px = 120em
当视窗大于120em时,他也远远大于36em。这个时候在1x屏中加载large.jpg
:
视窗宽度(viewport width) > 120em
Okay,还有最后一种情况 !
2x,断点之后
视窗宽度(viewport width) > 36em
同时
视窗宽度(viewport width) > 640px ÷ (33.3vw × 2x) = 960px = 60em
当视窗宽度大于60em的时候,在2x屏将加载large.jpg
视窗宽度(viewport width) > 60em
我们把他们放到媒体查询中:
( (min-device-pixel-ratio: 1.5) and (min-width: 20.001em) and (max-width: 35.999em) ) or
( (max-device-pixel-ratio: 1.5) and (min-width: 120.001em) ) or
( (min-device-pixel-ratio: 1.5) and (min-width: 60.001em) )
我们把什么时候加载medium.jpg
的计算留给大家当作一个练习。
现在我们按照前面的建议使用<picture>
来加载我们的图像:
<picture>
<source src="large.jpg"
media="( (min-device-pixel-ratio: 1.5) and (min-width: 20.001em) and (max-width: 35.999em) ) or
( (max-device-pixel-ratio: 1.5) and (min-width: 120.001em) ) or
( (min-device-pixel-ratio: 1.5) and (min-width: 60.001em) )" />
<source src="medium.jpg"
media="( (max-device-pixel-ratio: 1.5) and (min-width: 20.001em) and (max-width: 35.999em) ) or
( (max-device-pixel-ratio: 1.5) and (min-width: 60.001em) ) or
( (min-device-pixel-ratio: 1.5) and (min-width: 10.001em) )" />
<source src="small.jpg" />
<!-- fallback -->
<img src="small.jpg" alt="A rad wolf" />
</picture>
头晕了。
一堆的标签,而且还不支持device-pixel-ratio
在大于1和小于2之间。如果我们需要扩展到支持device-pixel-ratio
,我们还需要增加更多的标签。
增加标签还不是最糟糕的事情,最糟糕的是,我们改变了其中的任何变量——图片源尺寸、设备分辨率,或布局等。我们都需要重新计算。
那怎么办,不急,我们来看第二部分内容。
srcset + sizes = 无敌
因此得出,媒体查询可能并不是最好的工具用来处理响应式图片。那现在怎么办?
让我们回顾一下那些基本的响应式图像变量,并思考一下什么时候它们会变化以及谁会知道这些信息。
Variable | Known by author when she’s writing the code? | Known by browser when it’s loading the page? |
---|---|---|
viewport dimensions | no | yes |
image size relative to the viewport | yes | no |
screen density | no | yes |
source files dimensions | yes | no |
注意到在表格每一行中,当有一列内容是“yes”时,另外一列则是“no”:开发者和浏览器各自知道不同、互补的信息。我们是钥匙大师,它们是守门人;当我们的能力联合时……
怎么在中间架桥梁呢?
媒体查询就好像一个应急计划的集合。我们对浏览器说:“我不知道视图(viewport)到底有多大?但是,如果是这么大,那就用这个文件;如果更大点或者是视网膜屏幕,就用那个文件。但如果我切换到3列的布局……”我们根据所有的可能性给文件注上标签,表示浏览器将会知道的信息,我们虽然是编写代码的,却不知道。
从上面的实战中知道,还有很多工作要做。
如果我们跨越过它呢?
如果为了避免塞给浏览器混乱的应急计划信息,我们为何不能简单地告诉它不知道的信息呢?换句话说:图像怎么根据视图来变化尺寸和源文件的尺寸。我们都知道这些信息的。如果我们可以把这些信息告诉给浏览器的话,那岂不是万事俱备了?
是的,的确是可以这样省事的,这就是srcset
和sizes
属性和w
描述符在<picture>
标签中最新、最大的更新。再来看看表格如下:
Variable | Known by author when she’s writing the code? | Known by browser when it’s loading the page? |
---|---|---|
viewport dimensions | no | yes |
image size relative to the viewport | yes | yes! via sizes! |
screen density | no | yes |
source files dimensions | yes | yes! via srcset! |
在我们深入之前,我们先来说明三件事。
第一说明的是,浏览器目前还没实现这些属性,但未来前景发展是乐观的,但是细则还是不够清晰的(双关词还是不明确意思的)。因此,不能肆无忌惮地使用这些新属性。因为浏览器现在还没支持它们,但在不久将来就会实现的。
第二:在此之前,其实也有一个响应式图像计划叫srcset。我们现在谈到的全新计划也是基于srcset
属性的。新旧的srcset
都是使用w
描述符来形成资源URL列表的,但是两者代表的意思却完全不同的!旧的w
是一个缩写,组成了媒体查询:描述的是媒体查询视图的宽度。新的w
描述的是文件的宽度。我们将学习更多新w
的知识,但在现在,我只想从《黑衣人》中拿来抹除记忆棒(thing-a-majigs)来抹掉你们曾对srcset
和w
的认知。
都抹掉了吗?很好。
第三:如果你一直沿着或许给予希望于一个迭代的<picture>
版本,要知道新的<picture>
版本依然允许你使用媒体查询切换资源并且把分辨率描述符添加资源URL上。如果你正在实践艺术指导,或者是固定大小分辨率切换,那你绝对要使用这些特性。但是如果你只想简单地在位置上用方法使得图像压扁与伸展。
我想我们做好了准备工作,让我们重新处理一下我们的例子,但这次使用的是srcset
和sizes
。
让我们回顾一下,我们有3个版本图片:
larger.jpg(1024x768)
medium(640x480)
small.jpg(320x640)
……和一个切换从一到三列网格的36em断点。
演示代码如下:
<img src="small.jpg"
srcset="large.jpg 1024w,
medium.jpg 640w,
small.jpg 320w"
sizes="(min-width: 36em) 33.3vw,
100vw"
alt="A rad wolf" />
你可能会注意到这些演示代码是来自“图片”的规范,但里面却没有<picture>
标签,srcset
和sizes
属性在<img>
标签中依然生效的,例子中没有体现艺术指导、类型切换,你应该使用这样的例子来实现响应式图像。
跟标签<img>
一样,让我们一个一个地学习这些全新的属性。
src="small.jpg"
糟糕,这个并不是全新的属性啊!这个跟<img>
的src
代表意思一模一样的啊,作用就是但浏览器根本都认识srcset
和sizes
属性时,浏览器只会加载src
指定的资源。好,下一个!
srcset="large.jpg 1024w,
medium.jpg 640w,
small.jpg 320w"
这个属性的意思也是不言而喻的。srcset
的值是可用图像版本的URL列表;每个图片的宽度都用w
描述符来表明。因此如果你使用ps按照1024 × 768规格来“保存为网页……”,图片就标记为1024w
,容易理解吧。
是否留意到我们只是指定了宽度,为什么也不指定高度呢?我们布局中图片是限制于宽度的;它们的宽度通过CSS明确地设置的,但高度却不用。绝大部分的响应式图像本质上是根据宽度缩放的。因此细则上就是只通过宽度来设置响应式图像的,使得事情变得简单起来。
面向未来,有很多(只是我的观点,很好的)理由支持我们也应该使用h
描述符去描述文件的高度,只是不是现在。
让我再次强调当你使用srcset
而不是w
描述符可以处理1x/2x
分辨率描述符到资源,1x/2x
和w
不能混为一谈。千万不要在同一个srcset
中同时使用两者。
好了,我们讲完了srcset
和w
。
最后一点,浏览器需要选择一个多大的图像源呈现在我们的布局,为此,我们使用了sizes
,如演示代码:
sizes="(min-width: 36em) 33.3vw,
100vw"
使用语法如下:
sizes="[media query] [length], [media query] [length] ... etc"
我们使用长度与媒体查询配合,浏览器会检测每一个媒体查询,直到匹配到为止。使用查询配合的长度作为“选择资源”拼图的最后一块:图像渲染宽度或者相对于视图。
你说:“那是什么?”,“媒体查询吗?我知道你想说它们很糟糕?!”
我说过使用媒体查询来挑一个资源是件糟糕的事情,因为那根本就不是媒体查询做的事情;它们只是让浏览器在断点上执行相关页面的CSS。还记得在第一个例子中各种查询代码与页面布局断点(那个是36em宽)?我的意思是说:60em、20em、10em ——哪个都是!在sizes
中的断点应该正确地反映你的页面断点。当媒体查询表达式为true时,在布局中都给每张图片指定宽度。
现在浏览器都掌握了所有必要的信息,然后就会自行做如我们在第一部分做的计算的内容,但浏览器做的会比我们快很多。然后我们就可以去享受生活,吃美味的豌豆了。
并且,还记得我们之前媒体查询的例子只覆盖到1x和2x像素密度的屏幕?但上面的标签则可以处理任何设备像素密度(device-pixel-ratio)。不用再猜测哪些分辨率可以很多地支持或者不支持。即使在2016年出了一台4.862x智能手表,srcset
和sizes
也能轻松应对。
此外,这种方案使得浏览器有了更多回旋的空间。媒体查询在条件或真或假时,都会连接到资源;如果为真,浏览器就会加载相关的资源。sizes
和srcset
不会那么严格;当说明带宽很慢或者价格昂贵时,说明会促使浏览器加载更小的资源。
你说:“这一切听起来很完美”,并慢慢地点了点头。开始理解声明的好处,而不是条件的方法。“但是等等......长度是什么?”
长度可以是任何东西!长度也可以是绝对的(例如,99px
、16em
)或者相对的(33.3vw
,正如我们例子)。你也注意到,不像我们的例子那样,有很多布局可以结合绝对和相对单位。这就是强大支持calc()函数
的来源。假设我们添加12em宽的侧栏到3列布局中去。我们将适应大小属性,代码如下:
sizes="(min-width: 36em) calc(.333 * (100vw - 12em)),
100vw"
完成!
你疲惫地(但兴奋!)收获到这大量的知识,摸着下巴说:“好了,好了。然而,还有最后一件事就是:100vw是什么?难道你忘记一个媒体查询了吗?”
在规范的语言,在没有匹配媒体查询时,长度就是指“默认长度”。如果没有一个媒体查询皮匹配到,就会使用一张大的、占满banber图片,你的代码可能简单如下:
<img src="small.jpg"
srcset="large.jpg 1024w, medium.jpg 640w, small.jpg 320w"
sizes="100vw"
alt="A rad wolf" />
很简单,开始享受Peas吧。
译者手语:整个翻译依照原文线路进行,并在翻译过程略加了个人对技术的理解。如果翻译有不对之处,还烦请同行朋友指点。谢谢!
关于Spy
原名吴建杰,常用昵称“间谍”,现居广州。目前不断学习CSS3,HTML5等前沿技术,巩固js基础知识。在进行前端开发的过程中,追求HTML便签语义化,CSS的重用性,js封装等很多性能优化的操作,一切以用户体验为基础,不断地追求性能优化。希望与更多的同行朋友一起共勉:个人博客,新浪微薄。
如需转载,烦请注明出处: