容器长宽比,这个话题在站上也有相关的文章介绍,最早出现于Responsive Web Design中,主要用来处理img
、iframe
、video
和object
这些元素的自适应问题。简单点讲,就是根据容器的宽度,按照宽高比例自动计算出容器的大小。并且让图片,视频之类能自适应容器。另外记得在知乎上有一个问题“移动端布局,div
按比例布局,宽度为百分比,但又想让高度和宽度一样,即让div
为正方形,怎么做布局呢?”,其实解决方案在前面的教程已提到过:
既然有相应的解决方案,继续花时间来说,是不是有点多余。那么这个问题又回到了CSS的根源上:在Web中,使用CSS解决问题,往往不只有一种方案,只有更适合的方案。
这两天看到@Chris Coyier特意也整理了一篇《Aspect Ratio Boxes》文章,里面有新的方案值得我们思考,特别是CSS自定义属性的部分。那我们再次花时间将相关方案整理在一起,仅供学习与参考。
方案一:基于宽度的百分比
首先介绍的方案是基于容器width
给padding
一个百分比。这也是最早的一个方案。主要的原理是基于元素的padding-top
或padding-bottom
是根据元素的width
进行计算的。假设你有一个div
容器,它的宽度是500px
,你想让其高度也是和宽度一样,也就是说宽高比例是1:1
。这个时候借助padding-top
或者padding-bottom
的值为100%
,就可以计算出容器div
的高度是500px
。
这种方案有一个必要条件,容器div
的height
为0
,同时box-sizing
为border-box
,不然的话,容器不能带有border
。现在我们可以想象一下,如果容器div
的宽度又是一个百分比值,这样一来就可以保持容器高度跟宽度始一致。另外再想象一下,如果我们的padding-bottom
或padding-top
不是100%
,而是56.25%
,其实这就是一个完美的宽高比16:9
,也就是9 / 16 * 100% = 56.25%
。如此一来,你可以根据你自己的设计比来进行调整。
而这样的场景仅适合容器中放置背景图片:
<div class="aspect-ratio-boxes"></div>
.aspect-ratio-boxes{
overflow: hidden;
height: 0;
padding-top: 56.25%;
background: url(/images/happy-birthday.svg);
}
注:如果背景图片不是SVG文件,那还需借助于background-size
或者object-fit
来处理。比如设置为cover
这样的值。
回到我们的问题中来,很多时候,我们的图片比例并不是和容器比例一样,比如在这个示例中,有可能不是16:9
。就算是一个SVG,假设这个SVG的viewBox = "0 0 1127.34 591.44"
,这也意味着它本质上是一个1127.34×591.44
图像,它的比例是1127.34:591.44
。或者说它也有可能是一个328×791
的图形。
我想这种现象应该是很常见的,一个随机图像不一定符合预期的长宽比。那么问题就来了,对于一个任何长宽比,我们将如何实现?
任何可能的长宽比计算
对于固定的长宽比,比如16:9
,它是完美的,我们也无需头痛。但事实上,往往并不如此,总是充满了随机性,这样也就表示比例也是随机性的。如果我们无法掌握随机性的比例,那么就会造成一个图像或视频被裁剪或者被拉伸(也有可能是被挤压)。这样对于追求完美的同学而言,是无法接受的。
那么需要自问一下,有没有办法通过CSS的计算,实现任何可能的长宽比计算呢?
值得庆幸的是可以的。在CSS中有一个神奇的calc()
函数,它可以做一些基本的数学计算。拿上面的示例来说,SVG的长宽是1127.34×591.44
,可以通过calc()
计算出padding-top
或者padding-bottom
的值:
padding-top: calc(591.44 / 1127.34 * 100%);
这个时候可能有同学会提,使用calc()
在客户端进行计算,会不会有性能问题?事实会不会呢?其实我回答不了,因为我没有进行过这方面的论证,同时也没有看到相关论证的文章。如果你对这方面感兴趣的话,不仿论证一下。除此之外,我的小伙伴说,任何性能问题随着硬件的发展都不会是问题。既然如此,我们忽略所谓的性能问题,回到这里。我们实际中使用calc()
时,记得把单位带上,就上面的示例而言,我们的图片单位是px
,此时在Sass这样的处理器中,可以直接这样计算:
padding-top: 591.44px / 1127.34px * 100%;
实际使用就是这么的简单。calc()
都可以不使用了。
如果你对CSS有所了解的话,上面这样使用,很有可能是仅仅适合用于背景图像,如果我们的容器用不了背景图像。也就是说,容器里放置的是一个img
或者说是一个iframe
、video
。那就会产生一个新问题。
如果有内容,那么容器设置了
padding-top
会把容器的内容往下推;如果设置的是padding-bottom
,会把内容往上推。
接下来我们需要解决的就是这个问题。
如何在设置了padding的时候不把内容往下(上)推
就上面的示例而言,如果在div
容器设置了padding-top
(或者padding-bottom
)会造成容器的内容往盒子外推,此时设置了overflow:hidden
时,溢出的内容就会看不见了。为了解决这个问题,首先会想到的是position:absolute
。
假设我们有这样的一个示例:
<div class="aspect-ratio-box">
<iframe class="aspect-ratio-box-inside" src="https://www.youtube.com/embed/upPCohrJcbw?showinfo=0&modestbranding=1" frameborder="0" allowfullscreen></iframe>
</div>
使用对应的CSS:
.aspect-ratio-box {
height: 0;
overflow: hidden;
padding-top: 591.44px / 1127.34px * 100%;
background: white;
position: relative;
}
.aspect-ratio-box-inside {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
这是对于img
、video
、iframe
或者object
的一个较好的解决方案。但如果我们容器.aspect-ratio-box-inside
并不是这些元素,而是一些文本内容呢?我想你肯定会想到,将会出现什么问题?
很显然溢出容器的内容被裁切掉了。这个时候较好的解决方案就是把overflow:hidden
换成overflow:auto
。但也是美中不足,会出现滚动条。既然如此,有没有较好的解决方案呢?先不回答,咱们先来尝试下面这样的一种方案:借助CSS的伪元素来做容器的宽高比例。
.aspect-ratio-box {
background: white;
}
.aspect-ratio-box::before {
content: "";
width: 1px;
margin-left: -1px;
float: left;
height: 0;
padding-top: calc(591.44px / 1127.34px * 100%);
}
.aspect-ratio-box::after { /* to clear float */
content: "";
display: table;
clear: both;
}
来看一个示例效果吧:
从效果中可以告诉我们,这样处理方式,如果内容不超出容器的时候,容器的大小还具有对应的宽高比,如果内容超出容器的时候,会扩展容器的高度,让内容能足已展示。这样处理是不是更完美一些。
事实上,前面介绍的这些方法,我们在以前的文章中都有介绍过。但这篇文章具有营养之处是下一节内容。前面花这么多篇幅主要是让大家对宽高比具有一个更形象的理解。那就进入下一节吧。
使用CSS自定义属性
至于什么是CSS自定义属性,这里不做过多的介绍,如果从示接触这方面的内容,可以点击这里进行了解。接下来的内容,我假装你了解了CSS的自定义属性。首先在:root
里声明一个全局变量:
:root {
--aspect-ratio: 1 / 1;
}
在实际使用的时候,可以借助CSS自定义属性的局部变量来覆盖全局变量。比如:
<div style="--aspect-ratio:815/419;">
</div>
<div style="--aspect-ratio:16:9;">
</div>
<!-- even single value -->
<div style="--aspect-ratio:1.4;">
</div>
CSS的样式可以这样写:
[style*="--aspect-ratio"] > :first-child {
width: 100%;
}
[style*="--aspect-ratio"] > * {
height: auto;
}
@supports (--custom:property) {
[style*="--aspect-ratio"] {
position: relative;
}
[style*="--aspect-ratio"]::before {
content: "";
display: block;
padding-bottom: calc(100% / (var(--aspect-ratio)));
}
[style*="--aspect-ratio"] > :first-child {
position: absolute;
top: 0;
left: 0;
height: 100%;
}
}
这个时候,如果借助vw
这样的单位,在容器上做一定的处理:
[style*="--aspect-ratio"]{
width: 50vw;
margin: 20px auto;
background:orange;
}
你将看到一个完美的效果:
改变浏览器视窗大小,你可以看到如下的一个效果:
想出这样的CSS处理方式的人是不是天才呀。这样的思路值得我们去思考。上面的示例演示的是iframe
的方式,事实上,上面的方式适合img
、video
之类的。如果回到前面的示例,如果是文本内容呢?我想我不多说,你也能找出类似的解决方案。当然,你可能会好奇上面的CSS代码是什么意思?对于CSS的老司机而言,上面的代码不是问题,对于新同学而言,你只要理解了CSS的属性选择器、CSS的伪元素、CSS自定义属性以及CSS的calc()
和@supports
就可以很好的理解了。
其他的解决方案和思路
CSS自定义属性的方案已经让我们感觉眼前一亮了。但除了上面介绍的一些方案,还有其他的解决方案,比如CSSplus,他就提供了一个Aspecty特性,可以在你的代码中直接使用:
div {
background: lime;
--aspect-ratio: 478/239;
}
这种方法需要引入一个JS文件。这是其中不足之处。除此之外,@sgomes写了一个CSS-aspect-ratio的CSS文件。你只需要将这个文件引入到你的项目中,你也可以很好的处理宽高比。比如
npm
npm i --save-dev css-aspect-ratio
或者在你的CSS文件中:
@import https://unpkg.com/css-aspect-ratio@1/css-aspect-ratio.css;
当然,你也可以将这个文件保存到你的本地,更疯狂的是,直接把这里面的代码Copy到你的CSS中。有了这个前提,你在项目中这样使用即可:
<div class="aspect-ratio"
style="width: 768px; --aspect-ratio-w: 4; --aspect-ratio-h: 3;">
<img src="kitten.jpg" alt="A cute kitten">
</div>
另外还要一个就是PostCSS的插件:PostCSS Aspect Ratio。如果你的构建工具中已经使用了PostCSS,通过下面的命令就可以将这个插件安装到你的构建工具中:
npm install postcss-aspect-ratio --save
比如你要使用的宽高比例是16:9
,那可以这样使用:
HTML
<div class="aspect-box">
<div class="aspect-box__content">
<!-- Any content you like, very useful for video and image elements. -->
</div>
</div>
CSS
/* Input. */
.aspect-box {
position: relative;
background: lime;
aspect-ratio: '16:9';
}
/* Output. */
.aspect-box {
position: relative;
background: lime;
box-sizing: border-box;
}
.aspect-box > * /* This targets .aspect-box__content */ {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
box-sizing: border-box;
}
.aspect-box:before /* This pseudo element uses the padding trick to set the height. */ {
position: relative;
display: block;
content: "";
padding-top: 56.25%;
box-sizing: border-box;
}
如果你想在一定的比例上稍做调整,比如在4:3
的比例上,容器高度少个20px
,可以这样使用:
/* Input. */
.aspect-box {
position: relative;
background: lime;
aspect-ratio: calc('4:3' - 20px);
}
/* Output. */
.aspect-box {
position: relative;
background: lime;
box-sizing: border-box;
}
.aspect-box > * {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
box-sizing: border-box;
}
.aspect-box:before {
position: relative;
display: block;
content: "";
padding-top: calc(75% - 20px);
box-sizing: border-box;
}
是不是感觉很NB。如果你使用了,你也就变得NB了。不仿试试看。
总结
这是我见过,也是实战过的容器宽高比例的实现方案。不管是哪种方案,其基本原理都是不变的,和最原始的方案一样,借助于padding-top
或者padding-bottom
来实现。最简单的原理,容器的padding-top
(或padding-bottom
)的百分比是根据容器的width
来计算的。唯有不同之处是,实现的手段不一样,那是因为我们CSS技术发展的变化。或者说开发者特别聪明,除了原生的开发,还可以借助一些工具,比如说JS插件,PostCSS插件之类的。最后再提一下,如果我们容器自身是一个百分比单位,或者说是一个视窗单位,比如示例中用的vw
。那么我们就能更好的实现一些自适应的布局。特别是在响应式设计当中,就一两年前,要实现这样的效果还是很痛苦的。
最近也在思考一种更适合移动端的布局方案,其中思路就是借助于这篇文章的相关知识,目前正在实测阶段,如果通过测试之后,我们就可以告别以前所使用的REM方案(也就是《使用Flexible实现手淘H5页面的终端适配》分享的方案)。如果你感兴趣,欢迎持续关注相关更新。如果你对宽高比有其他的经验,欢迎与我们一起分享。
参考文档
如需转载,烦请注明出处:https://www.w3cplus.com/css/aspect-ratio-boxes.html