宽高比框

Avatar of Chris Coyier
Chris Coyier

DigitalOcean 为您的旅程的每个阶段提供云产品。 立即开始使用 $200 免费信用额度!

前几天我遇到一个需要制作宽高比友好框的小问题。 这并不是什么新鲜事。 我想,最初的功劳要追溯到 2009 年,Thierry Koblentz 的 Intrinsic Ratios,并一直流行至今,甚至适用于其他类型的文章内容,例如 Uncle Dave’s Ol’ Padded Box

让我们一起探索一下这个概念,因为有很多内容值得讨论。

核心概念:百分比填充基于宽度

即使这有点反直觉,比如垂直填充。 这不是一个黑客技术,但它确实很奇怪:padding-toppadding-bottom 基于父元素的 width。 因此,如果你的元素宽度为 500px,padding-top 为 100%,那么 padding-top 将为 500px。

这难道不是一个完美的正方形吗,500px × 500px? 没错,它是一个正方形! 一个宽高比!

如果我们强制元素高度为零(height: 0;)并且没有边框。 那么填充将是影响高度的盒子模型的唯一部分,我们将得到我们的正方形。

现在,想象一下,我们没有使用 100% 的顶部填充,而是使用了 56.25%。 这恰好是一个完美的 16:9 比例!(9 / 16 = 0.5625)。

现在我们有了 一个友好的宽高比框,它在流体宽度环境中效果很好。 如果宽度改变,高度也会改变,元素会保持这个宽高比。

用例:背景图像

也许我们制作了一个 文字排版锁住。 它用于文章的标题,因此使用 <h1> 标签是有意义的。

<h1>
  Happy Birthday
</h1>

我们可以将 <h1> 标签设为宽高比框,并将排版锁住作为背景图像应用。

h1 {
  overflow: hidden;
  height: 0;
  padding-top: 56.25%;
  background: url(/images/happy-birthday.svg);
}

但我在上面关于宽高比的描述有点错误。 它实际上并不是一个 16:9 的图像。 我只是从一个图片网站下载了这个图形。 它恰好是一个具有 viewBox="0 0 1127.34 591.44" 属性的 SVG,这意味着它本质上是一个 1127.34 × 591.44 的图像(在宽高比方面)。 或者它可能是一个 328 × 791 的图像。

我想说,任何随机图像都不符合非常特定的预定义宽高比的情况非常常见……

任何可能的宽高比的数学

正方形和 16:9 的比例很好,但用于这些比例的值只是简单的数学运算。 宽高比可以是任何东西,它们通常是完全任意的。 视频或图像可以裁剪成任何尺寸。

那么,我们如何计算上面 1127.34 × 591.44 SVG 的 padding-top 呢?

一种方法是使用 calc(),像这样

padding-top: calc(591.44 / 1127.34 * 100%);

不久前有人告诉我,在这里使用 calc() 可能“更慢”,但我从未见过任何证据。 我想,是的,计算机确实需要计算一些东西,所以在一个与不需要计算的情况的正面交锋中,计算会更慢。 但一个数学问题对于计算机来说似乎并不是太大的工作量。 例如,流行的英特尔奔腾 III(1999 年发布)可以达到 2,054 MIPS 或“每秒百万条指令”,所以它可以非常快地完成除法运算。 现在,芯片的速度快了 50 倍。

如果我们使用像 Sass 这样的预处理器,我们可以提前进行计算

padding-top: 591.44px / 1127.34px * 100%;

无论哪种方式,我都喜欢将数学运算保留在作者的代码中。

如果填充将所有内容向下推,如何将内容放在里面?

我们在上面的演示中隐藏了内容,让内容被推出去,并隐藏了溢出。 但如果需要一个宽高比框,同时将内容保留在框内呢? 这有点棘手。 我们需要将它定位回原位。 绝对定位可以胜任这个任务。

假设我们现在只使用文本,但仍然需要一个宽高比框。 我们需要一个用于绝对定位的内部包装器。 让我们使用具体的类名

<h1 class="aspect-ratio-box">
  <div class="aspect-ratio-box-inside">
    Happy Birthday
  </div>
</h1>

然后执行定位

.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%;
}

为了好玩,让我们在这里全力以赴,居中文本并调整其大小,使其与框一起缩放

<h1 class="aspect-ratio-box">
  <div class="aspect-ratio-box-inside">
    <div class="flexbox-centering">
      <div class="viewport-sizing">
          Happy Birthday
      </div>
    </div>
  </div>
</h1>

还需要一些其他类来进行样式设置

.flexbox-centering {
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}
.viewport-sizing {
  font-size: 5vw;
}

用例:视频

这可能是所有这些宽高比框技术中最常见和最实用的用例。 HTML5 <video> 并没有问题,因为它表现得像 <img> 一样具有宽高比。 但网络上的许多视频都放在 <iframe> 中,这些 <iframe> 不会随宽高比进行缩放。

这正是 FitVids 所要解决的问题。 它会找到页面上的视频,找出它们的独特宽高比,然后将这些相同的 CSS 概念应用于它们,使它们能够流体宽度,同时保持其独特的宽高比。

FitVids 基于 jQuery,但也有原生 JavaScript 选项,例如 Ross Zurowski 的 这个

但是……如果内容太多怎么办?

回到一会儿的任意(可能为文本)内容。

我们实际上是在这里设置高度,这在使用 CSS 时应该总是闪烁红色警示灯。 网页喜欢向下增长,设置固定高度是对这种自然运动的阻碍。

如果内容超过了空间,我们就进入了糟糕的设计领域

糟糕的设计领域

也许我们可以做一些事情,比如 overflow: auto;,但这可能导致尴尬的设计领域。

伪元素策略

我认为,这已经成为处理具有完全任意内容的宽高比框的最佳方法。 Keith Grant 在他的 文章 中对此进行了很好的描述。 Marc Hinse 在 2013 年给出了 解释和演示

使用这种技术,你可以获得宽高比框,而不需要太多标记,并且在内容超过高度时仍然安全

诀窍是将百分比填充应用于伪元素,而不是框本身。 你将伪元素浮动,这样里面的内容就可以很好地放在里面,然后清除浮动。

.aspect-ratio-box {
  background: white;
}
.aspect-ratio-box::before {
  content: "";
  width: 1px;
  margin-left: -1px;
  float: left;
  height: 0;
  padding-top: 591.44px / 1127.34px * 100%;
}
.aspect-ratio-box::after { /* to clear float */
  content: "";
  display: table;
  clear: both;
}

看看它如何更加安全

还有一个视频

使用自定义属性

这可能是所有想法中最酷的!

Thierry Koblentz 最近写了一篇关于它的文章(删除链接,因为 Thierry 的网站已失效。),并感谢 Sérgio Gomes 的想法。

要使用它,你只需为需要它的元素设置一个作用域为该元素的自定义属性

<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"] > img {  
  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%;
  }  

请允许我引用 Thierry 的分步解释

  • 我们使用 [style*="--aspect-ratio"] 作为钩子来定位相应的框
  • 无论是否支持自定义属性,我们都会拉伸内部框
  • 我们确保图像的高度来自其内在比例,而不是其 height 属性
  • 我们将容器设置为一个包含块(这样内部框会参考祖先进行定位)
  • 我们创建一个伪元素来用于“填充黑客技术”(正是这个元素创建了宽高比)
  • 我们使用 calc()var() 根据自定义属性的值计算填充
  • 我们将内部框的样式设置为与其包含块的尺寸相匹配

其他想法和工具

Lisi Linhart 向我推荐 了 Ratio Buddy,非常酷

请注意,它使用了伪元素,但仍然绝对定位了内部容器。这有点奇怪。你可能要么跳过伪元素并将填充直接放在容器上,要么使伪元素浮动,这样你就不用那个内部容器了。不过,我喜欢这种方法的简单易用。

Tommy Hodgins 有 CSSplus ,其中包含 Aspecty ,它专门用于此目的,假设你喜欢使用 JavaScript 解析并更改你的 CSS 的方法。


事实上,这些年来我见过很多现实世界中使用纵横比框的例子。如果你有使用经验,请分享一下!