理解和设置宽高比
渲染到页面上的每个元素都有高度和宽度,因此也有宽高比,即宽度和高度之比。媒体对象的自然尺寸,即在没有任何尺寸调整、缩放、放大或边框应用的情况下的尺寸,被称为其自然尺寸或固有尺寸。元素的固有尺寸由元素本身决定,而不是通过应用格式(如 box sizing)或设置边框、外边距或内边距宽度来决定的。
在开发网站时,你经常希望能够将元素的宽度设置为视口或父容器尺寸的百分比,并让高度按比例变化,从而根据视口的大小保持特定的宽高比。对于像图片和视频这样的替换元素,保持特定的宽高比不仅是创建响应式网页设计所必需的,也是提供良好用户体验的重要组成部分。为资源设置宽高比可以防止加载抖动(jank)——即在页面已经绘制后媒体加载时发生的布局偏移,由于没有为资源预留空间而导致重排。
使用 CSS,你可以根据宽高比调整替换元素和非替换元素的尺寸。在本指南中,我们将学习 aspect-ratio 属性,讨论替换元素和非替换元素的宽高比,然后研究一些常见的宽高比用例。
aspect-ratio 属性如何工作
CSS aspect-ratio 属性值定义了元素盒子的首选宽高比。该值可以是 <ratio>、关键字 auto,或两者的空格分隔组合。
<ratio> 是宽度与高度的比率,顺序是先宽后高。它由两个正的 <number> 值,中间用正斜杠(/)分隔,或单个 <number> 来表示。当使用单个数字时,它等同于将比率写为 <number> / 1,也就是宽度除以高度。
以下值都是等效的
aspect-ratio: 3 / 6;
aspect-ratio: 1 / 2;
aspect-ratio: 0.5 / 1;
aspect-ratio: 0.5;
以下值也都是等效的
aspect-ratio: 9/6;
aspect-ratio: 3/2;
aspect-ratio: 1.5;
auto 关键字的效果取决于它所应用的元素是否是替换元素。对于具有固有宽高比的替换元素,auto 表示应使用其固有宽高比。在所有其他情况下,auto 值表示该盒子没有首选宽高比。在这两种情况下,这都是默认行为,就好像没有应用 aspect-ratio 属性一样。
当值同时包含 auto 关键字和 <ratio> 值时,例如 aspect-ratio: auto 2 / 3; 或 aspect-ratio: 0.75 auto;,auto 值将应用于具有自然宽高比的替换元素,而指定的 width / height 或 <number> 比率则用作首选宽高比。
你会注意到在上述定义中出现了“首选”一词。设置的 aspect-ratio 值并非总是会生效。aspect-ratio 属性设置的是一个“首选”宽高比,因此只有在盒子的至少一个尺寸为自动时才会生效。
当高度和宽度,或者说内联尺寸和块级尺寸都被显式设置时,aspect-ratio 属性值将被忽略。在这种情况下,不允许任何维度自动调整尺寸——首选尺寸已被明确设置——所以 aspect-ratio 属性没有效果。当你同时声明内联和块级尺寸时,这些声明会优先。
对于替换元素,如果你没有为任一维度明确设置一个值(auto 除外),那么这两个维度都将默认为其固有尺寸(任何 aspect-ratio 值都不会被应用)。aspect-ratio 将应用于没有明确设置维度的非替换元素,因为非替换元素要么是固有尺寸,要么是外部尺寸,其大小取决于其内容、容器、盒模型属性等。
当一个元素渲染到页面上时,如果没有应用 CSS 且没有包含 HTML 尺寸属性,用户代理将以其自然尺寸渲染该对象。
调整替换元素的宽高比
像 <img> 和 <video> 这样的替换元素会被具有固定尺寸的媒体所替换,因此它们具有固有的宽高比。以光栅图像为例,如 JPEG、PNG 或 GIF。如果你在页面上放置一张图片,并且没有通过 <img> 属性或 CSS 设置高度或宽度,它将以其固有尺寸显示。
这是一张 220px 的正方形图片,没有应用任何 CSS;它以其固有或默认尺寸显示。
如果替换内容是自动调整尺寸的,或者你只为一个维度提供了尺寸,例如设置了 width 的值,浏览器会自动调整另一个维度(在这里是高度),同时保持媒体的原始宽高比。
在此示例中,仅对图片设置了 width,因此用户代理保留了其宽高比。同一张图片重复三次,分别以不同的宽度显示:55px、110px,以及通过 width: auto 值显示为其自然尺寸 220px。
只有当你为两个维度都提供尺寸时,才存在扭曲替换元素的风险。例如,在一张图片上设置 width: 100vw; 和 height: 100vh; 会创建一个可变的宽高比;当视口的宽高比与图片的自然宽高比不同时,图片会显得被拉伸或压扁。
在此示例中,同一张图片重复三次,明确指定了相同的 height 值(110px),但 width 值不同(55px、110px 和 220px)。
我们通过同时设置 height 和 width 有意地扭曲了图片:我们压扁了第一张,拉伸了第三张。
我们本可以使用 CSS 的 aspect-ratio 属性来创建同样扭曲的效果,方法是设置单个维度(而不是两个或都不设置)并提供一个非 1(或 1 / 1)的值。你可能不想这样做,但知道这是可能的也很好。
img {
height: 90vh;
aspect-ratio: 3;
}
我们声明了一个维度;100vh 是示例 <iframe> 视口的完整高度。要使 aspect-ratio 应用于替换元素,必须只设置一个维度。设置两个或都不设置都行不通。
使替换元素适应其容器
为了使替换元素适应其容器的尺寸,同时保持其固有的宽高比,可以将 object-fit 属性值设置为 cover 或 contain。这会调整替换元素的大小,并将其裁剪以“覆盖”容器,或者以较小的尺寸显示,完全“包含”在其中。
在此示例中,将正方形图像放置在一个由三个项目组成的网格中,每个项目的宽高比为 5 / 2。
首先,我们创建一个包含三个项目的容器,每个项目包含一张图片。
<div class="grid">
<div>
<img
src="https://mdn.github.io/shared-assets/images/examples/progress-pride-flag.jpg"
alt="Pride flag" />
</div>
<div>
<img
class="cover"
src="https://mdn.github.io/shared-assets/images/examples/progress-pride-flag.jpg"
alt="Pride flag" />
</div>
<div>
<img
class="contain"
src="https://mdn.github.io/shared-assets/images/examples/progress-pride-flag.jpg"
alt="Pride flag" />
</div>
</div>
接下来,我们将容器指定为网格,其中每个项目的宽高比为 2.5(5/2),最小宽度为 150px。因此,最小高度将是 60px。然而,最终的宽度和高度由示例的 iframe 的宽度决定,而 iframe 的宽度将基于你的视口大小。
.grid {
display: grid;
gap: 20px;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
font-size: 0; /* to minimize whitespace */
}
div div {
aspect-ratio: 5 / 2;
background-color: #cccccc;
}
然后,我们调整图片的大小,并对最后两张图片设置 object-fit 属性。
img {
height: 100%;
width: 100%;
}
.cover {
object-fit: cover;
}
.contain {
object-fit: contain;
}
只有第一张图片被扭曲(拉伸)了。我们本可以使用 object-fit 的 fill 值来创建同样的效果。cover 图片横跨容器的整个宽度,垂直居中,并被裁剪以适应容器。contain 值确保图片被包含在容器内,水平居中,并缩小以适应容器。
为非替换元素定义宽高比
虽然替换元素的宽高比默认会保持,但调整非替换元素的固有尺寸通常会改变其宽高比。例如,相同的内容在宽屏或宽父容器中可能显示为三行,但在窄屏或窄容器中则可能显示为八行。
在此示例中,同一段引用分别显示在 200px 和 600px 宽的容器中,并且设置了一个正方形,其高度与其 200px 的宽度相匹配。
为了突出通过尺寸维度设置非替换元素宽高比的问题,请在 auto 和 visible 之间切换 overflow 属性。
blockquote {
width: 200px;
}
blockquote:nth-of-type(2) {
width: 600px;
}
blockquote:nth-of-type(3) {
height: 200px;
}
虽然可以通过设置两个维度并隐藏溢出内容来为非替换元素定义宽高比,但 CSS 的 aspect-ratio 属性提供了明确的宽高比支持。这意味着即使你不知道内容或屏幕尺寸,也可以设置特定的宽高比。
在下一个示例中,我们通过在非替换元素 <blockquote> 上设置 aspect ratio: 1 来渲染正方形盒子,而不管文本的宽度如何。
blockquote {
inline-size: max-content;
aspect-ratio: 1;
}
每个盒子都定义了一个维度:inline-size(在水平语言中即宽度)被设置为 max-content,这将尺寸设置为足够宽以容纳内容而无需换行。第二个维度,在这里是 block-size 或 height,被设置为与第一个维度相同的长度。这是通过 aspect-ratio 属性实现的。我们将元素盒子的期望宽高比定义为 1,这与 1 / 1(正方形)相同。这使得块级方向的尺寸与元素的宽度相匹配,而无需使用 height 或 block-size 属性。
在这些示例中,尺寸被明确地设置在元素本身上。处理非替换元素时,当没有明确设置尺寸维度时,宽高比就会发挥作用。
根据容器尺寸创建一个圆形
非替换块级元素的内联尺寸是其容器内容盒的大小。因为它们默认具有尺寸,所以不需要为 aspect-ratio 属性设置显式尺寸就能生效。
在此示例中,我们有一个 200px 宽的容器 <div>,其中每侧包含 5px 的内边距。因此,内容盒的内联尺寸为 190px。在不为嵌套的 <p> 元素设置高度或宽度的情况下,我们知道其内联尺寸是 190px。设置了 aspect-ratio: 1 后,该段落将是 190px 高,除非有可见的溢出内容使其更高(但实际上没有)。
<div> 元素的高度没有明确设置,但它包含了 190px 高的段落、顶部和底部的 5px 内边距,以及 <p> 的默认顶部和底部外边距的总高度。因此,它比它的宽度要高。两个元素都设置了 50% 的 border-radius,所以容器是一个椭圆形,而子元素因为 aspect-ratio 为 1 且没有明确定义内联或块级尺寸,所以是一个圆形。
<div><p>Hello world</p></div>
div,
p {
border-radius: 50%;
}
div {
width: 200px;
padding: 5px;
border: 1px solid black;
background-color: #66ccff;
}
p {
aspect-ratio: 1;
text-align: center;
border: 10px solid white;
background-color: #f4aab9;
}
要使 <div> 成为一个圆形,我们可以将 height 和 width 设置为相同的值,或者设置 aspect-ratio: 1 并将 overflow 设置为 auto 或 hidden。另外,我们也可以简单地用 margin-block: 0 移除段落的外边距。下面展示了这两种选项。
<div><p>Hello world</p></div>
<div><p>Hello world</p></div>
div,
p {
aspect-ratio: 1;
border-radius: 50%;
}
div:first-of-type {
overflow: hidden;
}
div:last-of-type p {
margin-block: 0;
}
常见的 aspect-ratio 用例
让我们看几个在创建响应式设计时,可以使用 aspect-ratio 来解决一些常见挑战的情景。
使外部资源具有响应性
所有内容都应该是响应式的,即使这些内容是第三方嵌入的,例如来自 TikTok、YouTube 或 Instagram 的视频。你用来嵌入这些外部视频的代码片段通常会创建一个 <iframe>。
虽然 <video> 元素通常会采用其媒体文件的宽高比,但 iframe 元素缺乏这种能力。这就带来了一个挑战,即如何确保 <iframe> 在保持其所含视频宽高比的同时具有响应性。我们可以使用的一种技术是将 iframe 的宽度设置为其容器的 100% 或 100vw 以匹配视口宽度,而不管视口大小如何。然而,设置固定的高度可能会拉伸或压扁视频。相反,我们在视频的容器上设置 aspect-ratio,使其与视频的宽高比一致。问题解决了!
作为参考,在台式电脑或笔记本电脑上观看时,YouTube 视频的标准宽高比是 16:9,而 TikTok 和 Instagram 视频的宽高比是 9:16。
.youtube {
aspect-ratio: 16/9;
}
.instagram,
.tiktok {
aspect-ratio: 9/16;
}
我们可以在 @media 查询中使用 aspect-ratio 特性,并结合 aspect-ratio 属性来调整 iframe 及其所含视频的大小。这确保了视频内容始终尽可能大——占据视口的全部宽度或高度,而不管视口大小如何——同时保持特定的宽高比。
我们可以将横向的 YouTube 视频设置为视口宽度,将纵向的 TikTok 和 Instagram 视频 iframe 设置为视口高度。如果视口的宽高比大于 16:9,我们将 YouTube 视频设置为视口高度。如果视口窄于 9:16,我们将 Instagram 和 TikTok 视频都设置为视口宽度。
iframe.youtube {
aspect-ratio: 16/9;
width: 100vw;
height: auto;
}
iframe.instagram,
iframe.tiktok {
aspect-ratio: 9/16;
height: 100vh;
width: auto;
}
/* If the viewport is very wide but not very tall */
@media (aspect-ratio > 16 / 9) {
iframe.youtube {
width: auto;
height: 100vh;
}
}
/* If the viewport is very tall but not very wide */
@media (aspect-ratio < 9 / 16) {
iframe.instagram,
iframe.tiktok {
height: auto;
width: 100vw;
}
}
使网格单元格成为正方形
可以通过定义固定的列轨道尺寸来创建正方形单元格的网格,确保每行都与列轨道的大小相匹配。然而,当使用 auto-fill 创建响应式网格以在容器内容纳尽可能多的列轨道时,每个项目的宽度变得不确定。这使得确定合适的高度来创建正方形项目变得具有挑战性。
通过在项目上设置宽高比,我们可以确保当网格项目被布局时,每个网格项目的高度都与其宽度相等,从而创建出正方形的网格项目,而无论容器的尺寸如何。
在这个正方形网格项目的示例中,网格轨道是自动调整大小的,其尺寸取决于项目。每个项目的宽度至少为 95px,但可能会宽得多。无论宽度如何,每个项目都将是一个正方形,其高度由 aspect-ratio 决定,以匹配其宽度。
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(95px, 1fr));
}
.item {
aspect-ratio: 1;
}
为了使网格项目的内容不会超出 aspect-ratio 设置的首选高度,请将 min-height 设置为 0,并将 overflow 设置为除 visible 之外的值。这对于固有尺寸的内容有效。如果你的内容固有尺寸大于可用空间,请通过将 max-height(或 max-width,取决于内容)设置为 100%,来确保该内容不会大于网格项目。
.item {
min-height: 0;
overflow: auto;
}
.item > * {
max-height: 100%;
}
规范
| 规范 |
|---|
| CSS Box Sizing Module Level 3 # aspect-ratio |