当响应式图片变丑时应该如何处理
我结束了最近的“如何使用<picture>
和srcset
”教程,不知道你们是否也看过。给大家呈现了一些简单的知识来达到很好的教学目的,却没有让你为更多的不寻常的应用程序遇到的难看的效果做处理。
我遇到过一些比较冷僻的例子和需要当心的怪异模式,也发现了一些上面说的不寻常的应用程序。可能他们可以帮到你。
一些底层的东西
假设你已经对响应式图片有了一个基本的掌握。如果没有,建议你看一看Cloud Four的系列文章。
为什么烦恼?
如果随后的代码让你意识到处理响应式图片和它们的价值比起来有更多的麻烦,那么下面列出的几点会告诉你,过大的图片是多么的令你有多痛苦:
- 下载的大小:一个图片的像素密度的加倍会使它的面积成为原来的四倍并且使其文件大小变成原来的六倍!你不得不考虑这方面。
- CPU负载和电池消耗:图片也需要被解码。用CSS来收缩放大?现在浏览器收缩放大需要重新取样。这会随着图片的大小呈指数倍增大。
- 内存的使用:大的照片在移动浏览器中会造成颤抖。用CSS改变图片的大小并没有帮助;浏览器会保持完整的图片用来缩放。
一个过大的图片会浪费时间,带宽,数据,电池和系统资源。就算你不使用polyfill,你也应该使用响应式的图片因为56%的浏览器支持srcset
;别的方法都将会是不可靠的。
srcset是更好的
虽然<picture>
让人喜爱,但是一个普通的<img srcset>
更加灵活:节省带宽模式,浏览器可以自行启用。在未来它也会更得更佳友好,<picture>
只是和媒体查询一样。srcset
是聪明的选择。
不直观的行为和陷阱
像所有前端方面的东西一样,响应式图片带有很多边缘案例和无法预料的应用。我收集了一些曾经绊倒我的情况,希望它们不会再绊倒你。
媒体条件,不是媒体查询
响应式图片最重要和复杂的部分是sizes
属性。如果没有确切的告诉浏览器这个图片将会是多大的,你将会冒着增大比例导致的页面混乱或者缩小比例导致的页面膨胀的风险。所以自然而然地,sizes
语法是容易出错的。
在规范中,sizes
属性被设置为以逗号分隔的<source-size-value>
列表,<source-size-value>
可以分解成一个媒体条件(不是媒体查询!)和一个长度,像下面这样:
sizes="(media: condition) length,
(media: condition) length,
(media: condition) length"
长度就是如果对应的媒体条件为真时图片的宽度,它的值可以是700px
,或者calc()
表达式。
媒体条件差不多就是媒体查询。规范中将它们总结在一个正式的css语法中,但是对我们来说规则就是:
- 你不能用使用的媒体类型(
screen
,print
,tv
,等等)。 - 使用任何你喜欢的
(feature:value)
。 - 你可以用关键词
and
,or
和not
来连接多个媒体条件。不能用逗号代替or
。 - 如果用
or
或者not
关键词,你必须用括号将整个媒体条件包围起来。 - 如果媒体条件为空,它的计算结果总是为真。
我更喜欢举例说明。从例子中弄明白一个东西的概念比将一堆规则列在一起要简单的多。
一些有效的媒体条件:
(min-width: 500px)
(max-width: 300px) and (orientation: portrait)
((min-height: calc(50vw - 100px)) or (light-level: dim))
(是的,一个空的媒体条件也是可以的)
一些有效的长度:
200px
40em
calc(40rem - 10vw)
78.95vmax
一些有效的<source-size-value>
:
(min-width: 500px) 200px
(max-width: 300px) and (orientation: portrait) 40em
((min-height: calc(50vw - 100px)) or (light-level: dim)) calc(40rem - 10vw)
78.95vmax
很丑?强大吗?你猜。
如今,Web开发中从来就没有什么事是容易的,浏览器在使响应式图片生效之前不需要完全执行媒体条件语句。你可能需要候补的媒体条件来解决:
"((min-height: calc(50vw - 100px)) or (light-level: dim)) 500px,
(min-height: calc(50vw - 100px) 500px,
(light-level: dim) 500px"
一如既往,测试,测试,测试。我推荐placehold.it网站,可以明确地指出图片的宽度,减少不明确因素,并且为你节省每次检查currentSrc
的时间。
麻烦的 CSS 单位
注意: 在
<source-size-value>
中百分比是不允许的,为了避免关于它是相对于哪个元素造成混淆。vm
单位是可以使用的,因为它的大小是相对于视口的宽度的。
浏览器的图像预加载需要特有的URL去请求ASAP。它不会等待你需要下载和解析的CSS,它需要马上就能用的单位。而因为%
单位只有在你知道它的父元素有多大后才能生效,所以CSS需要去计算它。
rem
和em
是支持的,但是要小心使用。像内部的媒体查询转换与用户默认的font-size
相等。(经常是16px
,但不总是。)如果你考虑这个默认值,你应该能很好的运用它们。
如果你用html {font-size: 10px}
来简化运算,不要这样做。这是不尊重用户(注:就是用户如果设置了自己默认的浏览器字体大小,放大或缩小,这样做会导致不管用户如何改变浏览器默认字体大小都无法改变当前网页字体的大小),而且这会阻碍ems
在媒体查询上的威力。
如果想要在sizes
里用em
,也不能写html {font-size: 62.5%}
。如今有了预处理器,有些东西你不需要做,使用默认的用户font-size
是一个很好的实践。预防了大量的继承问题。
至于为什么你首先要用em
来定义一个图片的大小...
- 如果断点使用的是
em
,将会在媒体条件中将它们的作用体现出来。 - 如果用
em
设置文本容器尺寸来实现完美的一行文本的长度,并且里面的图片是max-width: 100%
,它们最终也是用em
来设置尺寸的。
至于那些奇怪的单位,像ch
或ex
或pc
...如果你使用它们,Tim Berners-Lee,Håkon W. Lie,Bert Bos都解决不了。
sizes
会影响图片如何展示
sizes
看起来好像只是提示CSS如何缩放图片,但是实际上它改变了图片将如何被显示并且是完全独立的。来观察一下:
如果屏幕很大,你会看到最后的图片是巨大的。它没有设置sizes
属性,这种情况下浏览器就会假定size="100vw"
。广泛的滥用这个默认值就会致使:为什么在一个响应式图片上省略sizes
不再有效的原因。
如果不是使用CSS来明确地设置图片的大小,就像设置了img { max-width: 100% }
,但不是所有的图片都会和它们的容器一样大,要小心并且检验来确保sizes
属性没有造成混乱。浏览器把选中的<source-size-value>
的长度看作<img width>
:如果不用CSS覆盖它,这就是它的宽度。
有媒体意识的图片
用<picture>
和media
属性的全部力量,可以使特别重要的图片(商标,图标,等等)最好的适应设备的性能和环境。
友好的打印
<picture>
<source srcset="logo-printer-friendly.svg" type="image/svg+xml" media="print, (monochrome: 1)">
<img src="logo.png" alt="Butt-Touchers Incorporated">
</picture>
你可能看过打印样式表教程指出:将图片设置为display: none
,用背景图片友好的代替打印版本。因为不是所有的浏览器都打印背景图和颜色(至少不是默认的),所以这个版本更强大。对于重要内容的图片,这种方式更好。
我也设定了设备中的图片不能显示颜色(monochrome: 1
),因为我们已经做了黑白版本来节省墨水。
有利于电子油墨显示器
具有update-frequency: slow
(大部分电子油墨显示器设备)的设备可能不会播放GIF图片,但是如果播放了,也会看起来非常的混乱和模糊,就像它在努力的跟上速度。
不论哪种情况,展示一个代表GIF的静止帧来代替原来的GIF会好得多。(但不是显示第一帧,第一帧反而经常是不适合的)所以这就是我们在这里所做的:
<picture>
<source srcset="reaction-frame.png" media="(update-frequency: slow)">
<img src="reaction.gif" alt="Say whaaaaat?">
</picture>
夜间模式和强光模式
<picture>
<source srcset="floorplan-dark.svg" media="(light-level: dim)">
<source srcset="floorplan-hicontrast.svg" media="(light-level: washed)">
<img src="floorplan.png" alt="The plans for how we'll renovate the apartment." longdesc="#our-plans" aria-describedby="our-plans">
</picture>
使用light level媒体查询,可以使图片在强的日光条件(washed
)下适应为高对比度和可见度,或者在暗的条件下(dim
)转化颜色为少分散的。
这些不是你将要争先实现的特性,但是得了解他们。友好打印是如今很有用的一个特性,light-level
正在增加对其的实现(Firefox已经支持它了),而不值钱的电子墨水显示有我们至今还没看到的硬件创新上的丰富的可能性。
你永远也猜不到;可能有一天你发现一个不起眼的媒体查询可以完美的运用到你的项目上,像color-index
或者scan
。
向后兼容的可能性
从一个HTML2.0 的<img>
开始:
<img src="giraffe.jpg" alt="A giraffe."
height="400" width="300">
实现的关键是一旦我们添加sizes
属性到这个<img>
标签中,理解<picture>
和基于宽度的srcset
的浏览器会忽略src
,height
和width
属性。
“基于宽度的srcset
?是什么?”喜欢猜想的朋友会猜想srcset
可能会有两种列表:
基于宽度的
srcset="giraffe@1x.jpg 300w, giraffe@1.5x.jpg 450w, giraffe@2x.jpg 600w, giraffe@3x.jpg 900w"
基于像素密度的
srcset="giraffe@1.5x.jpg 1.5x, giraffe@2x.jpg 2x, giraffe@3x.jpg 3x"
基于像素密度的srcset
(用x
描述符的)现在只能在Safari,一些早期版本的Chrome和Opera,和Microsoft Edge被兼容。基于宽度的更新版本(用w
描述符的)只在最新版本的Firefox,Opera和Chrome上被兼容。
如果想要使图片尽可能的具有响应式,有可能的话,这两种类型的srcset
都可用。当然我们可以做到!不要停止你的思维方式。如果用一个同时带有x
和w
描述符的srcset
,像这个一样:
srcset="giraffe.jpg 100w, giraffe@2x.jpg 2x"
这个时候浏览器会做些什么,但是我保证不会是你所想要的那些。我们不得不让多重的srcset
变得复杂,用<picture>
和<source>
。
首先,对所有的现代的浏览器,我会用带x
描述符的srcset
:
<img src="giraffe.jpg" alt="A giraffe."
srcset="giraffe@1.5x.jpg 1.5x,
giraffe@2x.jpg 2x,
giraffe@3x.jpg 3x"
height="400" width="300">
不像带w
描述符的srcset
,用x
描述符的srcset
会将src
属性也考虑进去。会假定它是1x
,所以你不需要再去定义它一遍。
对于x
srcset
,width
和height
属性只是继续做着它们要做的事;它所做的全部事情是在一个密度较高的屏幕上置换图片的资源。新的高分辨率资源将会占据和以前一样数量的"CSS 像素"。
接下来,将增强那些兼容用w
描述符的srcset
的浏览器的标记:
<picture>
<source srcset="giraffe@1x.jpg 300w,
giraffe@1.5x.jpg 450w,
giraffe@2x.jpg 600w,
giraffe@3x.jpg 900w"
sizes="300px" type="image/jpeg">
<img src="giraffe.jpg" alt="A giraffe."
srcset="giraffe@1.5x.jpg 1.5x,
giraffe@2x.jpg 2x,
giraffe@3x.jpg 3x"
height="400" width="300">
</picture>
“嘿,我记得你说过我们应该更喜欢简单的srcset!” 的确,我说过。如果你仔细观察单行的<source>
元素,我们没有用media
属性,所以浏览器将运用其srcset
就好像它是在<img>
元素上一样的效果!(注意:type="image/jpeg"
不是必须的,但是没有它HTML将不会是有效的)。
这个设置是尽善尽美的,除非你想要响应式图片在一个不兼容任何形式的srcset
的浏览器上也生效。我为我的网站提供了明智的回退src
。不让图片变得巨大。
srcset
约束高度和宽度
假设你有一个漂亮的大的全屏图片。是包含文本内容的图片,而且不是图片上的装饰内容,所以你用一个<img>
而不是一个background-image
。你想要让它具有响应式,因为手机上的全屏与在桌面上的全屏是不同的数量级的。
第一个问题是,你的图片表现的像contain
还是cover
?
如果你了解CSS的background-size
或object-fit
属性是如何工作的,就可以回答我刚刚问的那个问题。你的图片是否在不裁剪自己的情况下也可以尽可能的大,或者它是否会完全覆盖一个元素并且不留任何空的空间?如果你感到困惑,那就试着玩一下Davis Walsh的demo。
object-fit: cover
这个很简单:只是将sizes
设置为100vw
。
<img srcset="..." sizes="100vw" alt="A giraffe.">
object-fit: contain
这个是很困难的。
问题就是,srcset
现在只受宽度影响。有一个h
描述符出现了,但是现在对我们没有任何好处。那我们注定要使我们的图片变得过高,或是用<picture>
然后微观处理浏览器逻辑吗?
我对着苍天喊出我的绝望,换言之,向StackOverflow寻找主意。没想到Alexander Farkas,自2011年以来HTML5shiv的维护者,也是大量很好的响应式图片解决方法的作者或贡献者,突然出现并且第一个回答了问题:
我想得到(宽度/高度)比例中(1/2)
:
<img srcset="http://lorempixel.com/960/1920/sports/10/w1920/ 960w" sizes="(min-aspect-ratio: 1/2) calc(100vh * (1 / 2))" />
相当绝妙的方法。我将解释这简短的代码示例。
这整个运算的关键是图片的长宽比,没有这个,我们不能做任何事。
令人感激地,这个长宽比是直白的:图片的宽度除以它的高度。这让你得到一个“长宽倍数”(可能不是一个真正的术语)。实际的长宽比是倍数被写成一个简化的分数。经常被分成像16:9
的形式,但是在CSS里面是16/9
。
所以如果你有一个300px/500px
的图片和另一个600px/1000px
的图片,它们都有3:5
的长宽比,或是CSS中的3/5
。
假设有一个长宽比为4:5
的图片。在英语中,我们的逻辑是:
- 如果屏幕的长宽比更宽于(大于)图片的长宽比,图片将占满整个屏幕的高度。
- 否则,图片将占满屏幕的整个宽度。
用4/5
长宽比的图片,意味着:
<img sizes="(min-aspect-ratio: 4/5) 80vh, 100vw">
哇!80vh
是哪里来的?
这也是我曾经困惑的部分。首先要理解的是,我们任然是告诉浏览器图片将会是多宽。我们知道图片将是100vh
高(因为是全屏的图片),但是我们需要通知浏览器它所需要的宽度是什么。因为我们有了长宽比和高度,我们只需要求解得到宽度。
`Ratio = width ÷ height`
一个图片的长宽比是它的宽度除以它的高度。
`⅘ = width ÷ 100vh`
填上你知道的值
`width = ⅘ × 100vh`
分离出宽度变量
`width = 80vh`
简化
我希望不仅仅是Firefox 和Safari实现MathML。
第二个sizes
内容,单独的100vw
长度,是当视口(viewport)足够高到让图片成为宽度约束时使用的,意味着图片将占据整个屏幕的宽度。它实际上是可选的,因为如果在每个媒体条件都不为真时浏览器会默认采取100vw
。所以我们可以简化为sizes="(min-aspect-ratio: 4/5) 80vh"
。
这是最难的部分。毕竟srcset
只是一个图片和宽度的列表。因为图片要填满每个人的屏幕,不论屏幕的尺寸是多少。我们要囊括极小的300px
到很大的4000px
之间的所有范围。(尽管随着智能手表和新的5k屏幕的出现,这范围可能是不够的...)
<img sizes="(min-aspect-ratio: 4/5) 80vh"
src="giraffe.png"
srcset="giraffe@0.75x.jpg 300w,
giraffe.png 400w,
giraffe@1.5x.jpg 600w,
giraffe@2x.jpg 800w,
giraffe@2.5x.jpg 1000w,
giraffe@3x.jpg 1200w,
giraffe@3.5x.jpg 1400w,
giraffe@4x.jpg 1600w,
[...pretend I wrote out everything in between]
giraffe@9.5x.jpg 3800w,
giraffe@10x.jpg 4000w"
alt="A giraffe.">
这是不是一个过量的srcset
选择列表?大概是的。没有能计算出要包含什么宽度的高招,所以你不得不试验出哪些是适合你的网站的。确实是很多的标记,但是HTML负担增加与合适大小的图片相比会付出更多的代价。
在内联的SVG内的响应式的位图
我的个人用例是把栅格图片放到<svg>
中。你可能不会这样做,但是这篇博客的余下部分将是一个很好的例子来做说明。
总之,假设我像这样插入栅格图片:
<image xlink:href="giraffe.png" x="5" y="13" width="200" height="400">
<title>A giraffe.</title>
</image>
这个有两个问题:
- SVG没有任何像
srcset
的东西。你得到一个xlink:href
,你将会喜欢它。 - 浏览器预加载器没有像他们处理你的常规的
<img src>
一样获取它。这不是一个微不足道的减速;别的资源(scripts, 背景图片,等等)会在它之前加载,屏蔽了这个图片。
用<foreignObject>
我们可以一举解决这两个问题:
<foreignObject x="5" y="13" width="200" height="400">
<img src="giraffe.png" srcset="image@2x.png 2x, etc..." alt="A giraffe.">
</foreignObject>
它可以成为我们想要的一样的样子;<foreignObject>
接受任何的可以放入到<body>
中的HTML。除了在IE中(令人感激地,Edge支持<foreignObject>
)
修复IE中的问题
IE9,10和11都不支持<foreignObject>
,但是并不是没有希望的:用<switch>
来代替包含<image>
:
<switch>
<foreignObject x="5" y="13" width="200" height="400"
requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<img src="giraffe.png" srcset="image@2x.png 400w, etc..." alt="a giraffe">
</foreignObject>
<image xlink:href="giraffe.png" x="5" y="13" width="200" height="400">
<title>a giraffe</title>
</image>
</switch>
那个requiredFeatures
属性让IE知道<foreignObject>
元素需要URL里面描述的特性。IE不喜欢查一查它或任何别的东西,浏览器支持的URL是硬编码的。
一个讨人喜欢的副作用是IE的预加载器将会抓住他不会使用的src
里面的东西,但是这和他会使用的xlink:href
是相同的URL内容。令人高兴!
建立sizes
我有一个剩余的难题去处理。图片没有占据<svg>
父节点的全部空间。而且它们每一个都有自己的长宽比。
弄懂这个混乱的第一步是找出每个图片的尺寸占据<svg>
的尺寸的多少百分比。像这样的一个设置:
<svg viewBox="0 0 1400 1600" preserveAspectRatio="xMidYMid">
<image x="200" y="200" width="1000" height="1200"/>
</svg>
(在<svg>
元素上,我放置了preserveAspectRatio="xMidYMid"
,让viewBox
表现的像object-fit: contain
并使自己居中)
图片大概是<svg>
宽度的71.43%(1000÷1400)
,高度的75%(200 ÷ 1600)
。我们可以写一个基本的程序来为我们完成这个数学公式。但是组合这些信息成为一个可用的sizes
让我想避开它。这里是我的条件:
如果屏幕的长宽比宽于(大于)<svg>
的长宽比(7:8)
,<svg>
就是高度约束的。否则,是宽度约束的。
当<svg>
是宽度约束的:
图片展示的宽度 = 100vw × 71.43%
图片展示的宽度 = 71.34vw
<svg>
将会是100vw
宽,图片将会是其宽度的71.43%
。
当<svg>
是高度约束的:
图片展示的高度 = 100vh x 75%
图片展示的高度 = 75vh
<svg>
将会是100vh
高,图片的高度是其高度的75%
。
但是,我们需要的是它提供给sizes
的宽度。
长宽比 = 宽度 ÷ 高度
我们将再次使用长宽比的定义...
⅚ = 宽度 ÷ 75vh
替换我们知道的值
62.5vh = 宽度
解决。
处理完这些数字之后,我们的sizes
将会看起来像这样:
sizes="(min-aspect-ratio: 7/8) 71.43vw, 62.5vh"
这看起来不是太吓人,虽然我绕了很多的弯得到了它。将所有的都放到一起,最终:
<svg viewBox="0 0 1400 1600" preserveAspectRatio="xMidYMid">
<switch>
<foreignObject x="200" y="200" width="1000" height="1000" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<img src="giraffe.png" srcset="image@2x.png 400w, etc..." alt="A giraffe." sizes="(min-aspect-ratio: 7/8) 71.43vw, 62.5vh">
</foreignObject>
<image xlink:href="giraffe.png" x="200" y="200" width="1000" height="1200">
<title>A giraffe.</title>
</image>
</switch>
</svg>
是的,很复杂。但是这就是Web。
使情况变的最坏的不是完全占满整个视口的缩放
最后一个问题,我将要让浏览器界面来操纵这些SVG,所以我不会让整个视口(viewport)是可用的。我需要一个奇特的sizes
。
首先,我需要知道浏览器将要使用多少的空间。建立像这样的行为:
- 如果屏幕的宽度大于它的高(
orientation: landscape
),浏览器是全屏的。浏览器在水平方向上占据200px
,垂直方向上占据100px
。 - 否则,浏览器只占据垂直空间。将会是
200px
高。
所以现在我用四个盒子来进行分析:
- 浏览器视窗
- 一旦浏览器显示完成,视窗内的可用空间会被
<svg>
元素占据。 - SVG的视口("viweBox")
- 被
viewBox
约束的<image>
(如果这个过程的任何部分有可怕的浏览器漏洞,我将会是第一个发现的)
浏览器视窗
足够简单:
- 宽度 = 100vw
- 高度 = 100vh
- 长宽比 = 100vw/100vh
<svg>
元素
如果这个视窗的宽度大于高度(orientation: landscape
):
width = calc(100vw - 200px)
浏览器会占据200px的宽度,所以可用的宽度是整个的宽度(100vw)减去这个(200px)。
height = calc(100vh - 100px)
浏览器会占据`100px`的高度,我们像计算宽度那样计算高度。
长宽比 = calc((100vw - 200px) / (100vh - 100px))
将值带入等式来计算长宽比。
我用了calc()
,因为单位是不匹配的,只有浏览器知道如何将它们一起进行计算。
否则:
width = 100vw
当浏览器是orientation: portrait时根本就没有了水平浏览器占据的空间,所以就是100vw。
height = calc(100vh - 200px)但是还是有200px的垂直的浏览器占据空间。
长宽比 = calc(100vw / (100vh - 200px))
再次用值进行替换。
VIEWBOX
我们用上一次的<svg>
:
<svg viewBox="0 0 1400 1600" preserveAspectRatio="xMidYMid">
...
</svg>
不像浏览器视口和<svg>
元素,当定向改变的时候viewBox
的长宽比不会改变。这也是在viewBox
属性里面定义的。我们可以严格的使用1400/1600
,但是我会将其简化为7/8
。
至于高度和宽度,我们需要检查两个媒体条件:
- 浏览器视窗是宽的还是高的?
- 可用空间是否有比
viewBox
更宽还是更窄的长宽比呢?
第一个还是(orientation: landscape)
,但是浏览器使第二个变得复杂。理想上,我可以这样做:
(min-aspect-ratio: calc((100vw - 200px) / (100vh - 100px)))
...但是CSS需要X/Y
字符串值给它的aspect-ratio
查询。用calc()
是正确的。
作为替代,我们不得不对这个媒体查询进行逆向工程。查询长宽比终究只是为了处理视口宽度和高度的语法糖。像这样:
视口的长宽比 = 视口宽度 ÷ 视口高度 = 100vw ÷ 100vh
让我们用实际的数学符号来考虑一下我们想要的,而不是考虑CSS的min-
/max-
规则:
长宽比 ≥ 7/8
我们想知道如果视口的长宽比大于或等于7/8(viewBox的长宽比)会怎么样。
100vw ÷ 100vh ≥ 7/8
因为视口的长宽比等于100vw/100vh,所以我们代入上式。
100vw ≥ 7/8 × 100vh
两边都乘以100vh。
width ≥ 7/8 × 100vh
因为100vw是视口的宽度,再把它代回。
min-width: calc(7/8 * 100vh)
转化为CSS语法。
现在,如果这就是所有我们想要的,我可以预测每个人的浏览器用(min-width: 87.5vh)
。但是我到现在都还没有引入浏览器的空间需求。
- 当是
(orientation: landscape)
时,可用的宽度=100vw - 200px
,可用的高度 = 100vh - 100px
。 - 否则的话,
可用宽度=100vw
,可用高度=100vh - 200px
。
所以有这些复杂的因素,那么我们真正希望得到的长宽比是什么?
如果是 (orientation: landscape)
:
可用空间的长宽比 = (100vw - 200px) ÷ (100vh - 100px)
我们想要找出这个是不是大于7/8。
(100vw - 200px) ÷ (100vh - 100px) ≥ 7/8
像这样。 我们需要单独分离出100vw或是100vh那么我们就可以将其转化成视口尺寸的宽度或者高度。
100vw - 200px ≥ 7/8 × (100vh - 100px)
两边都乘以(100vh - 100px)。
100vw ≥ (7/8 × (100vh - 100px)) + 200px
两边都加200px。
width ≥ (7/8 × (100vh - 100px)) + 200px
将100vw转化为宽度。
min-width: calc((7/8 * (100vh - 100px)) + 200px)**
转化成CSS语法。(现在这是一个很丑的媒体条件。)
别的情况:
可用空间的长宽比 = 100vw/(100vh - 200px)
第二节,与第一节相同。这时候浏览器不那么的复杂。
100vw ≥ 7/8 × (100vh - 200px)
两边都乘以(100vh - 200px)。
width ≥ 7/8 × (100vh - 200px)
将100vw转化为宽度。
min-width: calc(7/8 * (100vh - 200px))
转化为CSS语法。
好的,很棒。现在我们有了我们的媒体条件的一半。所以我们需要这些的每一个的viewBox
的尺寸大小:
"(orientation: landscape) 和 (min-width: calc((7/8 * (100vh - 100px)) + 200px))"
"(orientation: landscape)"
"(min-width: calc(7/8 * (100vh - 200px)))"
""
好,让我们从头开始看。
如果是"(orientation: landscape)和(min-width: calc((7/8 * (100vh - 100px)) + 200px))"
,viewBox
是高度约束的。意味着我们需要高度来计算出宽度。
viewBox的高度 = 可用的高度 = calc(100vh - 100px)
viewBox会占据<svg>元素整个的高度。
长宽比 = 高度 ÷ 宽度
7/8 = calc(100vh - 100px) ÷ 宽度
因为我们将viewBox的长宽比设置为7/8,所以我们可以把替换进算式中,宽度我们就把宽度算入到长宽比的算式中。
7/8 × width = calc(100vh - 100px)
两边都乘以*width*
width = calc(100vh - 100px) × 8/7
两边都除以7/8。
**width = calc((100vh - 100px) * 8/7)**
转化为CSS语法。
如果不满足第一个情况,而且是"(orientation: landscape)"
,那viewBox
是宽度约束的。
意味着viewBox
会占据<svg>
元素的全部宽度。因此,viewBox
的宽度也是calc(100vw - 200px)
。
如果上面两个情况都不是,是 "(min-width: calc(7/8 * (100vh - 200px)))"
,viewBox
是高度约束的。
height = calc(100vh - 200px)
<svg>元素占据的高度。
aspect-ratio = height ÷ width
7/8 = calc(100vh - 200px) ÷ width
像前面那样替换。
width = calc(100vh - 200px) × 8/7
这次我直接一步就把宽度变量分离出来。如果你忘了怎么得到的, 重新看一下第一步.
width = calc((100vh - 200px) * 8/7)
转化为CSS语法。
剩下别的情况,都是宽度约束的。 这时候,根本没有水平的浏览器空间,所以宽度为100vw
。
我们成功得到了想要的值!
THE IMAGE
和viewBox
一样,我们使用上次的<image>
:
<svg viewBox="0 0 1400 1600" preserveAspectRatio="xMidYMid">
<image x="200" y="200" width="1000" height="1200"/>
</svg>
我们正在最后的阶段。一旦我们得到了最后一个盒子的宽度值,我们就可以整合sizes
。
不像前面的那样,我不需要一步一步的求解方程式。(你千万别感到不满,这些让我明白了我究竟在做的是什么。)我们可以得到viewBox
的宽度值然后乘以图像在viewBox
中占的百分比。如下...
图片宽度 ÷ viewBox 宽度 = 1000/1400 = 71.42%
当然,如果你记得这篇文章最开头说的,我们不能用百分比,所以我们将用0.7142代替。将这些宽度放回到媒体条件中,瞧:
(orientation: landscape) and (min-width: calc((7/8 * (100vh - 100px)) + 200px)) calc((100vh - 100px) * 8/7 * 0.7142)
(orientation: landscape) calc((100vw - 200px) * 0.7142)
(min-width: calc(7/8 * (100vh - 200px))) calc((100vh - 200px) * 8/7 * 0.7142)
71.42vw
这就是我前面所做的得到的结果。
装配
将所有的都放到一起得到最后的结果:
<svg viewBox="0 0 1400 1600" preserveAspectRatio="xMidYMid">
<switch>
<foreignObject x="200" y="200" width="1000" height="1200" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<picture>
<source srcset="giraffe@0.75x.jpg 300w,
giraffe.png 400w,
giraffe@1.5x.jpg 600w,
giraffe@2x.jpg 800w,
giraffe@2.5x.jpg 1000w,
giraffe@3x.jpg 1200w,
giraffe@3.5x.jpg 1400w,
giraffe@4x.jpg 1600w,
giraffe@4.5x.jpg 1800w,
giraffe@5x.jpg 2000w,
giraffe@5.5x.jpg 2200w,
giraffe@6x.jpg 2400w,
giraffe@6.5x.jpg 2600w,
giraffe@7x.jpg 2800w,
giraffe@7.5x.jpg 3000w,
giraffe@8x.jpg 3200w,
giraffe@8.5x.jpg 3400w,
giraffe@9x.jpg 3600w,
giraffe@9.5x.jpg 3800w,
giraffe@10x.jpg 4000w"
type="image/jpeg"
sizes="(orientation: landscape) and
(min-width: calc((7/8 * (100vh - 100px)) + 200px))
calc((100vh - 100px) * 8/7 * 0.7142),
(orientation: landscape)
calc((100vw - 200px) * 0.7142),
(min-width: calc(7/8 * (100vh - 200px)))
calc((100vh - 200px) * 8/7 * 0.7142),
71.42vw">
<img src="giraffe.jpg" alt="A giraffe."
srcset="giraffe@1.5x.jpg 1.5x,
giraffe@2x.jpg 2x,
giraffe@2.5x.jpg 2.5x,
giraffe@3x.jpg 3x,
giraffe@3.5x.jpg 3.5x,
giraffe@4x.jpg 4x,
giraffe@4.5x.jpg 4.5x,
giraffe@5x.jpg 5x"
height="400" width="300">
</picture>
</foreignObject>
<image xlink:href="giraffe.png" x="200" y="200" width="1000" height="1200">
<title>A giraffe.</title>
</image>
</switch>
</svg>
如果我的需求不是支持详细准确的图片,我可以将上面所有的都替换成一个单独的<image xlink:href="giraffe.svg">
。但是我已经造就这样追求完美的性格了。
就这样,伙计们
我希望你不用再做我做过的这些事情,很不公平,由于你刚刚已经看了所有要做的事情,但是主要是因为我不会收你任何钱。(相信我。)但是如果你陷入了棘手的响应式图片的问题,希望这篇文章的某些内容可以给你一点点的引导。
本文根据@Taylor Hunt的《When Responsive Images Get Ugly》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:http://codepen.io/Tigt/blog/when-responsive-images-get-ugly。
如需转载,烦请注明出处:http://www.w3cplus.com/responsive/when-responsive-images-get-ugly.html