使用CSS的aspect-ratio实现宽高比缩放

发布于 大漠

在《图解CSS: 元素尺寸的设置》一文中主要介绍了W3C的 **CSS Box Sizing Module Level 3**的属性,即 用来设置元素大小的CSS的属性,比如我们熟悉的widthheightmin-content等。在今年上半年(大约是2020年5月份)该规范新出了一个Level 4版本,该规范中新增了stretchfit-contentcontain三个属性值,可以用于widthheight等属性;新增了aspect-ratio属性用来指定容器宽高比;还有就是指定容器内部尺寸contain-intrinsic-size,有点像min-contentmax-content能根据元素内容来决定元素大小。不过我们今天主要来和大家一起探讨其中的aspect-ratio属性,如果你感兴趣的话,请继续往下阅读。

什么是宽高比

aspect-ratio属性对应的就是 Aspect Ratio,它的意思就是宽高比,也常称为 纵横比,是几何形状在不同尺寸的比值。举个例子,当矩形方向为横向时的宽高比值,是其长边与短边的比率。常会用来描述图像或屏幕的宽度和高度的比率。通常表示为 x:yx × y,其中的 冒号乘号 表示中文的 之意。

上图展示了三种常见的画面宽高比对角线比较(黑线圆框)。 最宽的蓝框( 2.39:1)是电影常用的画面宽高比。 绿框(16:9)和接近正方的红框(4:3)是电视常用的标准比例。

就拿16:9来说,第一个数字总是指 宽度,第二个数字是指 高度。如果图像的比例与屏幕的比例不同,你可能无法看到整个图像。如果屏幕比图像窄,图像就不能合适地放入。

在Web开发中,宽高比对Web的设计或布局有较大的影响的。随着科技不断的发展,Web开发者面对的屏幕终端可谓是不计其数,但这些终端屏幕(比如电视、电脑和手机)的比例也大多数图像的比例是不同的。你可能需要花一点时间改变图像(或元素)的宽高比,以便它与你使用的平台或设备更匹配。比如YouTube和Facebook在视频上处理的方式:

因此,当你选择根据屏幕大小调整视频,用户将会在应用上获得更好的体验,这个是值得的。

另外,我们有时候也会有相应的需求,比如说某个元素的高度按一定的比例随着元素宽度的变化增大(或减小)。这些都将用到宽高比相关的特性,而接下来要和大家一起探讨的就是CSS如何根据宽高比来调整元素的大小。

aspect-ratio的Hack手法

aspect-ratio可以说是CSS中比较新的一个特性,目前得到主流浏览器支持的不多,但根据宽高比调整元素大小的需求是一直存在的。也就是说,在aspect-ratio还未得到浏览器支持的情况之下,社区中的一些大神就通过CSS的Hack手法(称得上 CSS的黑魔法 )实现了类似aspect-ratio的特性,而且方案有多种。这里通过几个小的Demo先让大家回顾一下这些黑魔法,如果你以前从未接触过,也可以当作一种CSS技巧学习。

最为常见的方案就是使用padding-top(或padding-bottom)来模拟aspect-ratio,比如宽高比是16:9,那么padding-top = 9 ÷ 16 × 100% = 56.25%padding-bottom计算也是如此)。使用padding-toppadding-bottom来模拟aspect-ratio时,有一个细节需要特别注意,元素自身的高度height值为0或不显式设置height值。

<!-- HTML -->
<aspectratio-container>
    <aspectratio-content></aspectratio-content>
</aspectratio-container>

/* CSS */
.aspectratio-container {
    width: 50vmin; /* 用户根据自己需要设置相应的值 */

    /* 布局方案可以调整 */
    display: flex;
    justify-content: center;
    align-items: center;
}

/* 用来撑开aspectratio-container高度 */
.aspectratio-container::after {
    content: "";
    width: 1px;
    padding-bottom: 56.25%; /*元素的宽高比*/
    margin: -1px;
    z-index: -1;
}

效果如下:

注意,CSS中的百分比计算是比较复杂的,比如说widthpadding-bottom以及padding-top取值为百分比时,它们都是相对于其父元素的width计算。有关于CSS中各个取值为百分比值计算方式详细介绍,可以阅读《CSS中百分比单位计算方式》一文。

不过这种结构方式有一个致命点,如果<aspectratio-content>内容足够多时,它自身的高度会大于<aspectratio-container>根据计算所得的高度,这样宽高比缩放就失去意义:

为了满足更多场景,我们需要在上面的代码上稍作调整:

.aspectratio-content {
    position: absolute;
    left: 0;
    top: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: 100%;
    z-index: 2;
}

效果如下:

这个时候,即使<aspectratio-content>超出容器高度,也不会有任何影响,也可以说真正达到宽高比缩放的效果:

如果你讨厌自己去计算的话,可以使用CSS的calc()函数,直接在padding-bottom(或padding-top)赋值时用calc()来替代。同样拿16:9来说,其实就是16/9,对应的数学计算是9 ÷ 16 × 100%,换成CSS的calc()可以是 calc(9 / 16 * 100%),为了更好理解,也可以换成calc(100% / (16 / 9))

.aspectratio::after {
    content: "";
    width: 1px;
    padding-bottom: 56.25%;
    padding-bottom: calc(100% / (16 / 9)); /* 等同于 56.25% */
    padding-bottom: calc(9 / 16 * 100%); /* 等同于 56.25% */
    margin: -1px;
    z-index: -1;
}

他们效果都是一样的:

在使用calc()函数计算的时候,我们还可以把 CSS自定义属性 引入进来,比如:

<aspectratio-container style="--aspect-ratio: 16 / 9">
    <aspectratio-content></aspectratio-content>
</aspectratio-container>

在计算padding-bottompadding-top时可以像下面这样:

.aspectratio-container::after {
    content: "";
    width: 1px;
    padding-bottom: calc(100% / (var(--aspect-ratio)));
    margin: -1px;
    z-index: -1;
}

效果如下:

在《CSS Houdini: @propert 注册自定义属性》中我们介绍了CSS Houdini的自定义属性(变量),基于@property注册CSS自定义属性的特性,我们可以给相应的宽高比变量指定对应的语法值,比如<ratio>

<ratio> = <number [0,∞]> [ / <number [0,∞]> ]?

这样做会让自定义的属性更严谨,除此之外,还可以给注册的自定义属性指定一个初始值。比如下面这样的一个示例:

@property --aspect-ratio {
    syntax: "<ratio>";
    initial-value: "4 / 3";
    inherits: false;
}

.aspectratio-container::after {
    content: "";
    width: 1px;
    padding-bottom: calc(100% / (var(--aspect-ratio)));
    margin: -1px;
    z-index: -1;
}

@propertysyntax到目前为止还没有<ratio>语法类型。

随着vw这样的视窗单位的出现,实现宽高比的效果也变得更简便一些。来看一个简单的示例,假设容器的宽度是50vw,你希望元素的高度根据宽高比16:9获得,那么可以像下面这样做:

<!-- HTML -->
<aspectratio-container></aspectratio-container>

/* CSS */

.aspectratio {
    width: 50vw;
    height: 28.125vw; /* 50 ÷ 16 × 9 = 28.125 */
    background-color: #09f;
}

效果如下:

使用视窗这样的相对单位来模拟aspect-ratio有利也有弊,好的一面是不需要额外的容器,也不需要借助伪元素来将容器撑高,不利的一面是容器宽度改变时,高度也需要相应的去计算。比如说,宽度从50vw变成30vw,即使是相同的宽高比16:9,他们计算出来的值是不同的。

还有一个很有创意的解决方案,使用的都是CSS新特性:视窗单位CSS Grid布局。简单说一下其中的实现原理:将容器.aspectration通过display:grid声明为一个网格容器,并且利用repeat()将容器划分为横向比例,比如16,那么每一格的宽度对应的就是100vw * 9 / 16 = 6.25vw。同样使用grid-auto-rows,将其设置的值和横向的值一样。在子元素上通过grid-columngrid-row按比例合并单元格。

.aspectration { 
    display: grid; 
    grid-template-columns: repeat(16, 6.25vw); 
    grid-auto-rows: 6.25vw; 
} 

.aspectration[data-ratio="16:9"] .content { 
    grid-column: span 16; 
    grid-row: span 9; 
}

效果如下:

@Michelle Barker在 CSS {In Real Life}就针对该方案做过详细的阐述,并且通过实例实现下图这样的效果。

实现上图效果的示例代码如下:

有关于CSS Grid和padding-bottom(或padding-top)相结合实现aspect-ratio效果相关的介绍还可以阅读@Chris Coyier早在2017年写的一篇教程《Aspect Ratios for Grid Items》,在该教程最末尾还提供了一个示例:

有关于使用CSS Hack实现宽高比更多的介绍可以阅读:

CSS的aspect-ratio

上面花了一些篇幅介绍了CSS Hack手段实现aspect-ratio特性的几种方案。而根据宽高比调整元素尺寸大小这样的需求又的的确确地存在,正因为有需求就有市,W3C规范为CSS提供了一个属性aspect-ratio(宽高比)。有了该属性之后,我们根据宽高比来调整元素大小就变得容易地多了,简单到像下面这样做即可:

.aspectratio {
    width: 50vh;
    aspect-ratio: 16 / 9;
}

效果如下:

在上面这个示例中,.aspectratio的宽高比(aspect-ratio)是16:9,因为它的width显式设置为50vh,所以最终的高度是50vh ÷ 16 × 9 = 28.125vh

是不是很简单?那么aspect-ratio是怎么一回事呢?在阐述aspect-ratio之前,我们有几个概念需要先理解。

可替换元素(Replaced Element)

可替换元素指的是内容不在 CSS格式化模型 范围内的元素,例如我们熟悉的<img>元素<iframe>元素。HTML的<img>元素的内容经常被它的src属性指定的图像所替代。被替换的元素通常具有固有的尺寸:固有的宽度固有的高度固有的比例。例如,位图图像具有以绝对单位指定的固定有宽度和固有高度(从绝对单位可以确定固有比例)。另一方面,其他文档可能没有任何内在尺寸(例如,空白HTML文档)。

CSS格式化模型不考虑被替换元素的内容;然而,它们的内在尺寸被用于各种布局计算。

不可替换元素是未被替换的元素,也就是说,它的渲染由CSS模型决定。

内在尺寸(Intrinsic Dimensions)

内在尺寸是指内在高度(Intrinsic Height)、内在宽度(Intrinsic Width)和内在宽高比(宽度和高度之间的比率)的集合,对于给定的对象,每一个都可能存在,也可能不存在。这些内在尺寸代表了对象本身的首选或自然大小;也就是说,它们不是使用对象的上下文的函数。CSS通常没有定义如何找到内在的尺寸

光栅图像(Raster Image)是具有所有内在尺寸(三个维度)的对象的一个例子。设计成缩放的SVG图像可能只有一个固有的宽高比;SVG图像也可以只使用固定有的宽度和高度来创建。另外,在CSS中的渐变,是一个根本没有内在尺寸的对象示例。另一个例子是嵌入的文档,比如HTML的<iframe>元素。通常,对象不能只有两个内在的尺寸,因为任意两个尺寸都会自动定义第三个尺寸。但是,某些被替换的元素类型(比如,表单控件)可以有固有的宽度和固有的高度,但没有固有的宽高比。

如果一个对象(比如一个图标)有多个尺寸,那么最大的尺寸(按面积计算)被当作它的固有尺寸。如果在该尺寸下有多个宽高比,或有多个宽高比但没有尺寸,则使用最接近默认对象尺寸的宽高比。

<ratio>类型

CSS的语法中有一个比率类型,它一般用<ratio>表示,表示两个数值的比值。它通常表示宽高比,将宽度(首先)与高度(其次)联系起来。其语法规则是:

<ratio> = <number [0,∞]> [ / <number [0,∞]> ]?

<ratio>语法中的第二个<number>是可选的,默认为1。但是,<ratio>总是与两个<number>出现,并且两个值之间用/分隔。<ratio>的计算值是提供的一对数字。

如果<ratio>中的任何一个数字是0(无穷大),它表示一个退化的比率,这个时候通常不会做任何事情。如果需要比较两个<ratio>,则用第一个数除以第二个数,然后比较结果。例如3/2小于2/1,因为3/2对应的是3 ÷ 2 = 1.52/1对应的是2 ÷ 1 = 2

两个<ratio>的插值计算是这样定义的:将每个<ratio>转换为一个数字,第一个值除以第二个值(因此,3/2的比率值将变成1.5),对结果取对数(因此,1.5将变成大约0.176。插值期间的结果通过对数的反转被转换回<ratio>,然后将结果解释为<ratio>,结果为第一个值,第二个值为1

例如,从5 / 13 / 2的线性插值进行到一半,结果大约是2.73 / 1(大约是11 / 43 / 1略高):

start(开始)      = log(5);                    // ≈ 0.69897
end(结束)        = log(1.5);                  // ≈ 0.17609
interp(插值函数)  = 0.69897*.5 + 0.17609*.5;  // ≈ 0.43753
final(结果)       = 10^interp;                // ≈ 2.73

有了这个概念之后,我们可以接着继续往下聊aspect-ratio属性。

众所周之,<img>是可替换元素,它通常具有固定的宽高比,CSS布局算法试图在调整元素大小时保留这一比例。而aspect-ratio属性允许我们为不可替换的元素指定这种行为,也允许更改被替换元素的有效宽高比

aspect-ratio语法

正如前面的示例所示,aspect-ratio语法规则非常简单:

aspect-ratio: auto || <ratio>

其默认值为auto。该属性可以运用于除了内联框和内部ruby或表格框之外的所有元素。

aspect-ratio属性为元素盒子设置一个首选宽高比,将用于计算自动大小和其他一些布局函数。aspect-ratio属性主要接受的值所代表的含义是:

  • auto :可替换元素使用该宽高比替代其内在的宽度高比,元素盒子没有首选的宽高比。涉及内在宽度比的尺寸计算总是与内容框(content-box)尺寸一起工作
  • <ratio> :元素盒子的首选宽高比是<ratio>指定的宽高比率。涉及首选宽高比的尺寸计算与box-sizing指定的盒子尺寸一起工作
  • auto && <ratio> :如果同时指定auto<ratio>,则首选的宽高比是指定的宽高比(即<ratio>),除非它是具有固有宽高比的可替换元素,在这种情况下使用该宽高比。在所有情况下,涉及此宽高比的大小计算总是与content-box一起工作

我们单独把<ratio>值拎出来说。比如上面的示例,aspect-ratio设置的值是<ratio>类型,即16 / 9。而<ratio>类型的值可以是:

  • 它通常是由/分隔线分隔的两个数字组成,比如16 / 9。其中第一个参数指定元素宽度,第二个参数指定元素高度
  • 它还允许只传入一个数字,比如1.7777777(即 16 ÷ 9 ≈ 1.7777777)。在这种情况下,第二个参数默认为1
  • 不允许为这两个数字都传入0
  • 分隔线/前后的空格不是必须的,因此16/9也是一个有效的<ratio>值,但这里更建议在/分隔线前后添加一个空格

aspect-ratio的使用

aspect-ratio属性使用和元素类型、盒子模型以及运用场景有着紧密关联。我们从简单的开始。

元素显式设置了尺寸

在《图解CSS: 元素尺寸的设置》一文中,我们可以获知,在CSS中显式给元素盒子设置尺寸大小的属性很多,比如widthheightmin-widthmax-widthmin-heightmax-height以及其相对应的 CSS逻辑属性

这些指定元素盒子尺寸的属性都可以和aspect-ratio使用,将会根据指定的<ratio>来重新计算元素盒子大小。

width + aspect-ratio

当元素盒子显式指定了其width值,并且同时指定aspect-ratio值时,元素盒子的高度height会根据aspect-ratio指定的<ratio>计算出来。比如:

.aspectratio {
    width: 300px;
    aspect-ratio: 4 / 3;
}

效果如下:

就这个示例而言,.aspectratio并没有显式设置其height值(相当于height: auto),在没有显式设置aspect-ratio值时,容器的高度根据其内容的高度来计算,但显式设置aspect-ratio值时,其高度会根据元素宽度和指定的宽高比计算得出,比如这个示例,计算出来的height等于225px。其计算过程如下:

height = width ÷ <ratio> = 300px ÷ 4 × 3 = 225px

虽然widthaspect-ratio结合重新计算出元素盒子的高度,但元素的内容高度超出该计算值时,内容的高度会撑高该容器:

这个时候如果在容器.aspectratio显式设置overflow的值是非visible时,其高度又将是widthaspect-ratio计算出来的值:

正如上面所看到的,widthaspect-ratio结合重新计算出元素盒子的高度,但也会受min-heightmax-height的影响。比如:

.aspectratio {
    width: 300px;
    aspect-ratio: 4 / 3;
    min-height: 300px;
}

虽然计算出来的height225px,但它小于min-height的值,这个时候.aspectratio元素的高度会取min-height的值:

再来看max-height的影响:

.aspectratio {
    width: 300px;
    aspect-ratio: 4 / 3;
    max-height: 100px;
}

虽然计算出来的height225px,但它大于min-height的值,这个时候.aspectratio元素的高度会取max-height的值:

在开发的时候width的值有可能会取%值。换句话说,如果使用了百分比值的widthaspect-ratio结合时同样也能计算出height,只是会分两步走:

  • 元素自身width会根据其父元素的宽度计算出自己的值
  • 元素基于新的width值和aspect-ratio计算出height的值

比如:

<!-- HTML -->
<div class="parent">
    <div class="aspectratio">4 : 3</div>
</div>

/* CSS */
:root {
    --width: 50%;
}

.parent {
    width: 300px;
    min-height: 300px;
}

.aspectratio {
    width: var(--width); /* 基于其父元素的width,50%计算出来的值是 150px */
    aspect-ratio: 4 / 3; /* height = width ÷ <ratio> = 150px ÷ 4 × 3 = 112.5px */
}

你可以尝试调整下面示例中的进度条,改变百分比值查看相应的效果:

特别声明,CSS中的百分比计算是一个复杂的过程,不同属性取百分比值时,它的计算方式不同(参考物),正如上面的示例所示,width取百分比值时,相对于其父容器(父元素)的width计算。有关于CSS百分比计算更详细的介绍,可以阅读《CSS中百分比单位计算方式》一文

height + aspect-ratio

这个场景指的是元素并没有显式设置width值,但可以通过设置的heightaspect-ratio计算出相应的width值,比如:

.aspectratio {
    height: 300px;
    aspect-ratio: 4 / 3;
}

效果如下:

.aspectratio并没有显式设置其width值(相当于width: auto),在没有显式设置aspect-ratio值时,容器的高度根据其内容的高度来计算,但显式设置aspect-ratio值时,其高度会根据元素宽度和指定的宽高比计算得出,比如这个示例,计算出来的width等于400px。其计算过程如下:

width = height × <ratio> = 300px × 4 ÷ 3 = 400px

在介绍widthaspect-ratio计算出容器height时,如果元素的内容(自身或后代元素)超过计算出来的height时,那么内容会撑开容器高度;但heightaspect-ratio计算出来的width时,如果元素内容有足够多,但也不会撑宽容器,内容只会沿着垂直方向溢出:

同样的,heightaspect-ratio计算出来的width也会受min-widthmax-width的影响。

注意,并不是说aspect-ratio会受min-*max-*相关值影响,因为就这里的示例场景,计算的是容器的widthheight,而在CSS中widthheight和其对应的min-*max-*同时出现时,有相应的权重关系。具体这方面的介绍请参阅《图解CSS: 元素尺寸的设置》一文

同样的,height的值也可以取百分比值。那么它的计算过程和width值取百分比值是相同的:

  • 首选height根据父元素的height计算出新的值 (元素的height属性取%值时,它的计算是相对于元素父容器的height计算)
  • 元素基于新的height值和aspect-ratio计算出width的值

比如:

<!-- HTML -->
<div class="parent">
    <div class="aspectratio">4 : 3</div>
</div>

/* CSS */
:root {
    --height: 50%;
}

.parent {
    height: 300px;
    min-width: 400px;
}

.aspectratio {
    height: var(--height); /* 基于其父元素的height,50%计算出来的值是 150px */
    aspect-ratio: 4 / 3;   /* width = height × <ratio> = 150px × 4 ÷ 3 = 200px */
}

效果如下:

width + height + aspect-ratio

如果在元素上同时显式的设置了widthheight的值为非auto值,那么这个时候即使同时设置了aspect-ratio也将无效,比如:

.aspectratio__wh {
    width: 30vw;
    height: 30vh;
    aspect-ratio: 4 / 3; /* aspect-ratio失效 */
}

.aspectratio__h {
    height: 30vh;
    aspect-ratio: 4 / 3; /* width为auto,计算出来的 width = height × <ratio> = 30vh × 3 ÷ 4 = 40vh */
}

.aspectratio__w {
    width: 30vw;
    aspect-ratio: 4 / 3; /* height为auto, 计算出来的 height = width ÷ <ratio> = 30vw ÷ 4 × 3 = 22.5vw */
}

aspect-ratio

上面我们看到的示例都是aspect-ratiowidthheight结合起来使用。实际中,我们可以仅仅在元素上使用一个首选宽高比,就是在元素上只设置aspect-ratio,不设置任何有尺寸大小有关的属性,比如widthheight之类。比如下面这个示例:

<!-- HTML -->
<div class="parent">
    <div class="aspectratio">
        <p>1:1</p>
    </div>
</div>

<div class="parent">
    <div class="aspectratio">
        <p>Occaecat deserunt elit officia aliqua est esse quis dolor proident pariatur. </p>
    </div>
</div>

<div class="parent">
    <div class="aspectratio">
        <p>Occaecat deserunt elit officia aliqua est esse quis dolor proident pariatur. Esse commodo ut ullamco laboris tempor eiusmod minim. Labore magna excepteur nulla sunt velit velit ullamco ad. Est aute Lorem velit laboris ut dolore cillum.</p>
    </div>
</div>

我们可以看到div.aspectratio的内容是不同的,并且我们在该容器上显式设置了aspect-ratio的值为1/1

.parent {
    width: 40vh;
    font-size: 16px;
    line-height: 1.5;
}


.aspectratio {
    aspect-ratio: 1 / 1;

    background-color: #09f;
    padding: 10px;
}

注意,为了更好的向大家演示,在每个div.aspectratio容器外嵌套了一个div.parent容器,并且显式指定该容器的宽度是40vh,即width: 40vh

回到div.aspectratio容器上来,我们在元素上并没有显示设置widthheight,那么这个时候其实相当于widthheight的值都是autoW3C规范就是这样定义的

这个时候容器自身的宽度和高度计算是一个复杂的过程,它们和 视觉格式化模型 有着紧密的关系:

  • 如果它自身是块元素(块盒),那么它的宽度和父容器宽度相同(相当于width: 100%),它的高度和内容有关,内容在垂直方向越多,它的高度就越高
  • 如果它自身是行内元素(行内盒),它么它的宽度和高度都和自身的内容有关,如果内容足够多,那么当内容撑开宽度等同于父容器宽度时,内容会断行,这个时候它的宽度就相当于父容器宽度

我们来看下图:

CSS中还可以使用display属性来改变元素默认的视觉格式化模型,有关于这方面的介绍可以阅读《Web布局:display属性》一文。

有了这个基础之后,你就会理解上面的示例为什么会有下面这样的效果:

.aspectratio元素的宽度与其父容器(通常情况)相同,其高度与包含内容所需的高度相同,但至少与它的宽度相同(因为aspect-ratio1 / 1,也可以根据widthaspect-ratio计算出相应的高度)。

这个时候,如果在.aspectratio上显式设置一个visibleoverflow属性时,包含过多内容的框也会保持aspect-ratio指定的宽高比(比如示例中的1:1),如果overflow显式设置的是autoscroll时,内容过多时会出现滚动条,如果overflow显式设置的是hidden,内容过多时,多出来的内容会被截取:

另外还有一个办法,就是设置min-height的值,比如min-height: 0,也能让元素保持<ratio>的宽高比,比如示例中的aspect-ratio1/1,让元素高度和宽度相同,如果内容高于容器高度时会溢出容器:

.aspectratio {
    aspect-ratio: 1 / 1;
    min-height: 0;
}

其实将min-height设置非0的值也能达到同等的作用,但有一点需要注意,如果min-height的值大于容器width时,那么width会基于min-heightaspect-ratio重新计算,并且始终保持<ratio>的宽高比:

大家可以尝试调整下面示例中的参数:

aspect-ratio遇上了min()max()clamp()函数

在CSS中,可以在widthheight使用 CSS的min()max()clamp()函数设置其大小:

width: min(4em, 80px) 
width: max(4em, 80px) 
width: clamp(1vw, 4em, 80px)
  • 使用min()设置一个最大值
  • 使用max()设置一个最小值
  • 使用clamp(MIN, VAL, MAX),其中MIN表示最小值,VAL表示首选值,MAX表示最大值

换句话说,aspect-ratio很有可能会和min()max()clamp()结合起来使用,比如:

.aspectratio__min{
    aspect-ratio: 4 / 3;
    width: min(50%, 200px);
}

.aspectratio__max{
    aspect-ratio: 4 / 3;
    width: max(50%, 200px);
}

.aspectratio__clamp {
    aspect-ratio: 4 / 3;
    width: clamp(200px, 50%, 400px);
}

Flex项目 + aspect-ratio

Flexbox布局中,如果在Flex项目中使用了aspect-ratio那么他的计算和flex-basisflex-shrinkflex-grow等属性相关,因为在Flexbox布局中,这三个属性对Flex项目的大小有着决定性的作用。除此之外,他们也受widthmin-widthmax-width等属性影响。因为在计算Flex项目时,这几个属性的组合有相应的权重:

首先根据contentwidthflex-basis来决定用哪个来决定用于Flex项目。如果Flex项目显式设置了flex-basis属性,则会忽略contentwidth。而且min-width是用来设置Flex项目的下限值;max-width是用来设置Flex项目的上限值

对于flex-growflex-shrinkflex-basis之间关系,可以用下图来描述:

如果我们在Flex项目中显式设置了aspect-ratio值时,Flex项目的height计算可能会经过:

  • 先由flex-basisflex-shrinkflex-grow等属性计算出每个Flex项目的宽度
  • Flex项目的高度会由其新宽度和aspect-ratio计算获得

比如下面这样的一个示例:

.parent {
    display: flex;
    justify-content: center;
    align-items: flex-start;
}

.aspectratio {
    aspect-ratio: var(--aspect-ratio);
    flex: 1;
}

你也可以尝试着调整Flex项目中各个参数来看相应的变化。

注意,Flex项目的尺寸计算是复杂的,如果你对这方面感兴趣,可以阅读下面几篇文章:

Grid项目 + aspect-ratio

在Grid的布局中,Grid项目也可以和aspect-ratio来计算其高度。这个有点类似于上一节中Flex项目和aspect-ratio计算Flex项目高度类似。

.parent {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 10px;
}

.aspectratio {
    aspect-ratio: var(--aspect-ratio);
    align-self: flex-start;
}

效果如下:

在Grid布局中,还可以使用span来合并单元格,这样其实也间接性的改变了Grid项目的宽度,那么基于aspect-ratio设置的<ratio>宽高比也会影响Grid项目的高度,比如下面这个示例:

.parent {
    display: grid;
    grid-template-columns: repeat(24, 1fr);
    gap: 10px;
}

.aspectratio {
    aspect-ratio: var(--aspect-ratio);
    background-color: #09f;
    align-self: flex-start;
}

div:nth-of-type(1) {
    grid-column: span 3;
}

div:nth-of-type(2) {
    grid-column: span 5;
}

div:nth-of-type(3) {
    grid-column: span 7;
}

div:nth-of-type(4) {
    grid-column: span 9;
}

div:nth-of-type(5) {
    grid-column: span 12;
}

aspect-ratio + attr()

在CSS中我们可以使用attr()函数来获取HTML的一些属性的值,比如attr(width)获取到HTML元素中width的值。我们在使用aspect-ratio可以和attr()结合起来使用:

<!-- HTML -->
<iframe width="800" height="600" src="https://www.youtube.com/embed/LuIHEev-yVg" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen style="--aspect-ratio: 800 / 600"></iframe>

/* CSS */
iframe {
    border: 10px solid #f90;
    aspect-ratio: var(--aspect-ratio);
    width: 50%;
    height: auto;
}

@supports (aspect-ratio: attr(width number) / 1) {
    iframe[width][height] {
        aspect-ratio: attr(width number) / attr(height number);
    }
}

效果如下:

尝试着调整浏览器视窗大小,你将看到下图这样的效果:

aspect-ratio其他情景

在上面介绍aspect-ratio示例的时候已经多次看到CSS自定义属性。比如:

:root {
    --aspect-ratio: 16 / 9;
}

.aspectratio {
    aspect-ratio: var(--aspect-ratio);
    width: 300px;
}

除了使用CSS原生的自定义属性之外,还可以使用CSS Houdini的自定义属性:

@property --aspect-ratio { 
    syntax: '<ratio>'; 
    initial-value: 16 / 9; 
    inherits: false; 
}

.aspectratio {
    aspect-ratio: var(--aspect-ratio);
    width: 300px;
}

但并没有生效。按照aspect-ratio的语法中我们可以知道其值是<ratio>类型

但是到目前为止CSS Houdini的自定义属性的<syntax>类型还没有<ratio>类型,因此我们上面的代码不生效:

而CSS的aspect-ratio除了可以接受/分隔的一对数字值之外,也可以接受一个数值,比如16/91.7777等同。换句话说,如果继续希望使用CSS Houdini的@property让其生效,我们可以考虑将syntax的值更换为<number>,比如:

@property --aspect-ratio { 
    syntax: '<number>'; 
    initial-value: 1.7777; 
    inherits: false; 
}

.aspectratio {
    aspect-ratio: var(--aspect-ratio);
    width: 300px;
}

如果你对CSS Houdini的自定义属性相关的知识感兴趣的话,还可以阅读:

前面多次提到过,aspect-ratio的值为<ratio>时,在/分隔线前后是<number>值,那么就可能碰到00/33/0的场景,也就是有可能值是0和无穷大()。当出现这两种情景时,aspect-ratio会取值为auto

<img> + aspect-ratio

前面的示例,我们大多数看到的都是不可替换元素使用aspect-ratio的场景(除<iframe>元素)。但在HTML中有些元素是可替换的元素,比如我们熟悉的<img>元素和嵌入的文档(比如<iframe>元素)等,这些可替换的元素其内容超出CSS格式化模型范围的元素。

就拿<img>来举例吧:

<img src="https://images.unsplash.com/photo-1546238232-20216dec9f72?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=2807&q=80" alt="" width="800" height="600">

我们显式地在<img>元素上显式的设置了widthheight的值。这个时候你使用浏览器开发者工具查看<img>时会发现下图这样的效果:

该图像有三个固有的尺寸:固有的宽度(2087px固有的高度(1394px固有的比例(2087/1394,除此之外,还有三个内在的尺寸:内在的宽度(800px内在的高度(600px内在的宽高比(800/600。我们还可以使用aspect-ratio来指定可替换元素的宽高比:

img {
    aspect-ratio: 16 / 9;
    width: 50%;
    height: auto;
}

这个时候,<img>的内在尺寸会根据aspect-ratio指定的比例和width重新计算。

示例中<img><body>子元素,截图时body的宽度是537pximgwidth50%,计算出来宽约是268.5px,显式设置的aspect-ratio值是16/9,根据相关计算,可以计算出<img>的高度约151px。最终效果如下:

媒体查询中的aspect-ratio

在CSS的媒体查询中也有一个aspect-ratio,它和我们前面介绍的aspect-ratio是不同的。CSS媒体查询中的aspect-ratio是用来测试 视窗的宽高比。其为一个范围,这意味着你可以使用min-aspect-ratiomax-aspect-ratio分别查询最小和最大的值。

下面的例子包含一个 <iframe> ,拥有它自身的视窗大小。调整的<iframe>宽高来观察aspect-ratio的变化。

/* 最小宽高比 */
@media (min-aspect-ratio: 8/5) {
    div {
        background: #9af; /* blue */
    }
}

/* 最大宽高比 */
@media (max-aspect-ratio: 3/2) {
    div {
        background: #9ff;  /* cyan */
    }
}

/* 明确的宽高比, 放在最下部防止同时满足条件时的覆盖*/
@media (aspect-ratio: 1/1) {
    div {
        background: #f9a; /* red */
    }
}

效果如下:

使用该特性也可以让你的应用根据视窗比例变化调整布局,比如@christopherjfoster在他的教程《Aspect Ratio Media Queries》中就提供了一个这样的示例

CSS媒体查询中除了aspect-ratio之外还有device-aspect-ratio用来指定设备的宽高比。可以是min-device-aspect-ratiomax-device-aspect-ratio

article {
    padding: 1rem;
}

@media screen and (min-device-aspect-ratio: 16/9) {
    article {
        padding: 1rem 5vw;
    }
}

不过device-aspect-ratio已被弃用,所以不建议在项目中使用该媒体查询特性。如果你对CSS媒体查询特性感兴趣的话,还可以阅读:

小结

aspect-ratio这个特性是一个非常有意思的特性,有了这个特性之后我们能真正的做到元素根据宽高比来调整尺寸。文章中通过示例向大家详细演示和阐述了aspect-ratio的使用。当然,还有一些临界点没有阐述清楚,但这些足够帮助大家理解该特性。如果你在这方面有相关的经验,欢迎在下面的评论中与我们共享。