探索 CSS 中宽度和高度的复杂性

Avatar of Uri Shaked
Uri Shaked

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元免费积分!

以下文章由 Uri ShakedMichal Porag 共同创作。

让我们探索 CSS 如何计算元素的宽度和高度尺寸的复杂性。这是基于无数个熬夜调试和 fiddling 与大量 CSS 属性组合,阅读规范,并试图找出为什么有些东西似乎以这种或那种方式运行。

但在开始之前,让我们先了解一些基础知识,以便我们都站在同一起跑线上。

基础

您有一个元素,您希望它宽度为 640px,高度为 360px。这些只是符合 16:9 像素比例的任意数字。您可以像这样显式地设置它们

.element {
  width: 640px;
  height: 360px;
}

现在,设计需要在该元素内部添加一些 padding。因此,您修改了 CSS

.element {
  width: 640px;
  height: 360px;
  padding: 10px;
}

现在元素的渲染宽度和高度是多少?我敢打赌你能猜到……它不再是 640×360px!它实际上是 660×380px,因为填充在每侧(即顶部、右侧、底部和左侧)添加了 10px,在高度和宽度上各增加了 20px。

这与 box-sizing 属性有关:如果将其设置为 content-box(默认值),则元素的渲染大小为宽度和高度加上 paddingborder。这可能意味着渲染大小比我们预期的要大,这很有趣,因为它可能最终导致元素声明的 widthheight 值与渲染的值完全不同。

这就是 CSS 盒模型 的强大之处。它像这样计算宽度和高度

/* Width */
width + padding-left + padding-right + border-left + border-right

/* Height */
height + padding-top + padding-bottom + border-top + border-bottom

我们刚刚看到的是如何计算 块级元素 的尺寸。块级元素包括任何自然占用可用完整宽度的元素。因此,本质上,元素包含多少内容并不重要,因为它的宽度始终为 100%,也就是说,直到我们改变它为止。想想 <p><article><main><div>许多其他元素

但是现在我们应该看看内联元素,因为它们在盒模型和如何计算其尺寸方面表现不同。之后,我们将看看父元素和子元素之间的关系,以及它们如何影响彼此的宽度和高度计算。

内联元素的奇特案例

正如我们刚刚看到的,任何元素的 paddingborder 都包含在元素的计算宽度和高度尺寸中。有趣的是,在某些情况下,widthheight 属性根本没有作用。在使用 内联元素 时,情况就是这样。

内联元素是指其宽度和高度由其包含的内容决定的元素。内联元素(例如 <span>)将完全忽略 widthheight 以及顶部和底部的 margin 属性,因为,嗯,内容决定了尺寸。在这里,有时一张图片可以帮助理解。

看看将块级元素嵌套在内联元素中是如何打破内联元素的形状的,仅仅因为块级元素不是由它包含的内容数量定义的,而是由可用空间的数量定义的。当我们在内联元素上添加边框时,您真的可以直观地看到这一点。看看内联元素是如何在段落出现的地方突然停止,然后在段落之后继续的。

span 元素看到段落,段落会中断 span 元素的内联流,并本质上从中跳出。太奇妙了!

但还有更多!看看内联元素是如何完全忽略 widthmargin 的,即使它们是在内联元素上直接声明的。

太疯狂了!

父元素和子元素

父子关系是一种常见的模式。父元素是指包含嵌套在其中的其他元素的元素。而这些嵌套的元素是父元素的子元素。

<!-- The parent element -->
<div class="parent">
  <!-- The children -->
  <div class="child"></div>
  <div class="another-child"></div>
  <div class="twins">Whoa!</div>
</div>

元素的宽度和高度在父元素和子元素的情况下变得非常有趣。让我们看看由此产生的所有有趣的小问题。

相对单位

让我们从相对单位开始。相对单位是通过其上下文或与其他元素的关系来计算的。是的,这有点复杂。 有几种不同的相对单位类型,但让我们以百分比为例。我们可以说一个元素的宽度为 100%。

<div class="child">
  <!-- nothing yet -->
</div>
.parent {
  width: 100%;
}

酷。如果我们将该元素放置在一个空白页面上,它将占用可用水平空间的 100%。而这 100% 的计算结果取决于浏览器的宽度,对吧?一个宽度为 1,500 像素的浏览器的 100% 是 1,500 像素宽。一个宽度为 500 像素的浏览器的 100% 是 500 像素宽。这就是我们所说的相对单位。实际的计算值由它使用的上下文决定。

所以,敏锐的读者可能已经在想:嘿,所以这有点像一个设置为父元素宽度的子元素。 这确实是正确的。子元素的宽度为 100% 将根据包含它的父元素的实际宽度来计算。

高度的工作方式非常相似:它是相对于父元素的高度。例如,两个具有不同高度尺寸但具有相同子元素的父元素会导致子元素具有不同的高度。

填充和边距

当我们查看其他属性(例如 paddingmargin)时,父子组合的宽度和高度变得更加有趣。显然,当我们为填充或边距指定百分比值时,它总是相对于父元素的宽度,即使处理垂直边缘也是如此

一些聪明的设计师利用它来创建宽度和高度相等的框,或者在页面调整大小后保持特定纵横比的框。这对于 视频或图像内容 尤其有用,但也可以以创造性的方式(滥用)。继续,在这个演示中,在可编辑元素中输入您想要的任何内容。无论添加了多少(或多少)内容,该框都会保持比例的高度和宽度。

这种创建纵横比框的技术被称为“填充技巧”。Chris 已经广泛介绍了它。 但是现在,随着 aspect-ratio 属性获得广泛的浏览器支持,我们没有太多理由去使用它。

display: inline 和 inline-block

既然我们已经了解了父元素和子元素尺寸是如何计算的,我们应该检查一下另外两个有趣属性,它们会影响元素的宽度:min-contentmax-content

这些属性告诉浏览器查看元素的内容以确定其宽度。例如,如果我们有文本:“hello CSS encyclopedia, nice to meet you!”,浏览器将计算该文本在屏幕上占用的空间,并将其用作宽度。

min-contentmax-content 之间的区别在于浏览器如何执行此计算。对于 max-content,浏览器假装它有无限的空间,并将所有文本放在一行中,同时测量其宽度。

对于 min-content,浏览器假装它没有空间,因此它将每个单词/子内联元素放在不同的行中。让我们看看它的实际效果

我们实际上在查看块级元素和内联元素之间的差异时,看到了max-content的实际应用。还记得吗?内联元素的宽度和高度只与其包含的内容一样大。我们可以通过在其上声明display: inline;来将大多数元素设置为内联元素。

酷!我们还有另一个武器:display: inline-block;。它创建一个内联元素,但在盒子模型中增强了块级计算。换句话说,它是一个尊重marginwidthheight的内联元素。两全其美!

循环百分比大小

最后一点讲清楚了吗?好吧,希望我不会因为这个让你困惑

在这个例子中,子元素的相对宽度为33%。父元素没有声明宽度。当子元素没有参照对象时,它的计算宽度是如何得到的呢?

为了回答这个问题,我们必须看看浏览器如何计算这个例子中元素的大小。我们没有为父元素定义一个特定的宽度,因此浏览器使用width初始值,即auto。由于父元素的display设置为inline-blockauto的行为类似于max-content。正如我们所见,max-content意味着父元素的宽度应该与其内部内容一样宽,即子元素内部的所有内容。

所以,浏览器会查看元素的内容(子元素)来确定其宽度。然而,子元素的宽度也依赖于父元素的宽度!天哪,这太奇怪了!

CSS 盒模型规范将此称为循环百分比大小。我不确定为什么它被称为循环百分比大小,但它详细介绍了浏览器必须执行的复杂数学运算,以(1)确定父元素的宽度,以及(2)将该宽度与子元素的相对宽度进行协调。

这个过程实际上非常酷,一旦你克服了数学难题。浏览器首先在应用声明的值之前,为子元素计算一个临时宽度。浏览器用于子元素的临时宽度是auto,我们看到它像max-content一样,它反过来告诉浏览器子元素需要与它包含的内容一样宽。现在,这并不是声明的33%的值。

浏览器使用这个max-content值来计算父元素的宽度。你会发现,父元素需要至少与它包含的内容一样宽,即子元素中以max-content表示的所有内容。一旦确定,浏览器就会返回到子元素,并应用在CSS中声明的33%的值。

它看起来像这样

现在我们知道了子元素如何影响其父元素的计算值。

M&Ms: min-max- 属性

嘿,你可能已经知道以下属性存在

  • min-width
  • min-height
  • max-width
  • max-height

好吧,这些属性与元素的宽度和高度也有很大关系。它们指定元素大小的限制。就像在说,“嘿,浏览器,确保这个元素的宽度/高度永远不会低于这个值,也不高于这个值。”

所以,即使我们已经为一个元素声明了一个明确的宽度,比如100%,我们仍然可以通过给它一个max-width来限制这个值

element {
  width: 100%;
  max-width: 800px;
}

这允许浏览器让元素占用它想要的尽可能多的空间,最多800像素。让我们看看如果我们翻转这些值,将max-width设置为100%width设置为800px,会发生什么

element {
  width: 800px;
  max-width: 100%;
}

嘿,看起来结果是完全一样的行为!元素会占用它需要的所有空间,直到它达到800像素。

显然,一旦我们将父元素加入进来,事情就开始变得更加复杂了。这与上面的例子相同,但有一个显著的变化:现在每个元素都是inline-block元素的子元素。突然之间,我们看到这两个例子之间存在着显著的差异

为什么会有差异?为了理解,让我们考虑浏览器如何计算这个例子中元素的宽度。

我们从父元素(.parent)开始。它有一个display属性设置为inline-block,我们没有为它指定一个width值。就像之前一样,浏览器会查看子元素的大小来确定其宽度。这个例子中的每个框都包含在.parent元素中。

第一个框(#container1)的百分比width值为100%。父元素的宽度解析为其内部文本的宽度(子元素的max-content),受我们通过max-width指定的限制,并且该值也用于计算子元素的宽度。

第二个框(#container2)有一个固定的宽度为800px,所以其父元素的宽度也为800px,正好足以容纳其子元素。然后,子元素的max-width相对于父元素的最终宽度(即800px)进行解析。因此在这种情况下,父元素和子元素的宽度都为800px。

所以,即使我们最初看到两个框在交换widthmax-width值时表现相同,我们现在知道情况并不总是这样。在这种情况下,引入一个设置为display: inline-block;的父元素会彻底改变这一切!

添加min()max()clamp()

min()max()clamp() 是三个有用的 CSS 函数,它们让我们可以以响应的方式定义元素的大小… 无需媒体查询

  • min(): 返回其参数的最小值。参数可以用不同的单位给出,我们甚至可以混合和匹配绝对单位和相对单位,比如 min(800px, 100%)
  • max(): 返回其参数的最大值。就像 min() 一样,你可以混合和匹配不同的单位。
  • clamp(): 用于同时执行 minmax 的简写函数: clamp(MIN, VAL, MAX) 解析为 max(MIN, min(VAL, MAX))。换句话说,它会返回 VAL,除非它超过了 MINMAX 定义的边界,在这种情况下,它将返回相应的边界值。

像这样。看看我们如何有效地使用单个 CSS 属性“重写”上面的 max-width 示例

.element {
  width: min(800px, 100%);
}

/* ...is equivalent to: */
.element {
  width: 800px;
  max-width: 100%;
}

这会将元素的宽度设置为 800px,但确保我们不会超过父元素的宽度 (100%)。就像之前一样,如果我们用一个 inline-block 父元素包装元素,我们可以观察到它与 max-width 变体不同的行为

子元素的宽度 (800px) 是相同的。但是,如果你放大屏幕(或使用 CodePen 的 0.5x 按钮缩小),你会注意到第二个父元素实际上更大。

这归结于浏览器如何计算父元素的宽度:我们没有为父元素指定宽度,并且由于子元素的 width 值使用相对单位,因此浏览器在计算父元素的宽度时会忽略它,并使用子元素的 max-content 子元素,该子元素由“非常长的…长的”文本决定。

总结

哇!真是不可思议,看似简单的widthheight实际上包含了很多内容。当然,我们可以为元素设置明确的widthheight值,但最终呈现的实际值通常会完全不同。

这就是 CSS 的美妙之处(坦率地说,也是让人沮丧的地方)。它经过精心设计,考虑了多种边缘情况。我的意思是,盒子模型本身的概念既奇妙地复杂,又优雅。我们还能在哪里明确地声明代码中的某件事,并让它以不同的方式解释?width并不总是宽度。

而且我们还没有触及影响元素尺寸的其他一些因素。现代布局技术,如 CSS Flexbox 和 Grid 引入了轴和轨道线,这些线也决定了元素的渲染尺寸。


作者: Uri ShakedMichal Porag