以下文章由 Uri Shaked 和 Michal 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
(默认值),则元素的渲染大小为宽度和高度加上 padding
和 border
。这可能意味着渲染大小比我们预期的要大,这很有趣,因为它可能最终导致元素声明的 width
和 height
值与渲染的值完全不同。
这就是 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>
和 许多其他元素。
但是现在我们应该看看内联元素,因为它们在盒模型和如何计算其尺寸方面表现不同。之后,我们将看看父元素和子元素之间的关系,以及它们如何影响彼此的宽度和高度计算。
内联元素的奇特案例
正如我们刚刚看到的,任何元素的 padding
和 border
都包含在元素的计算宽度和高度尺寸中。有趣的是,在某些情况下,width
和 height
属性根本没有作用。在使用 内联元素 时,情况就是这样。
内联元素是指其宽度和高度由其包含的内容决定的元素。内联元素(例如 <span>
)将完全忽略 width
和 height
以及顶部和底部的 margin
属性,因为,嗯,内容决定了尺寸。在这里,有时一张图片可以帮助理解。
看看将块级元素嵌套在内联元素中是如何打破内联元素的形状的,仅仅因为块级元素不是由它包含的内容数量定义的,而是由可用空间的数量定义的。当我们在内联元素上添加边框时,您真的可以直观地看到这一点。看看内联元素是如何在段落出现的地方突然停止,然后在段落之后继续的。
span 元素看到段落,段落会中断 span 元素的内联流,并本质上从中跳出。太奇妙了!
但还有更多!看看内联元素是如何完全忽略 width
和 margin
的,即使它们是在内联元素上直接声明的。
太疯狂了!
父元素和子元素
父子关系是一种常见的模式。父元素是指包含嵌套在其中的其他元素的元素。而这些嵌套的元素是父元素的子元素。
<!-- 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% 将根据包含它的父元素的实际宽度来计算。
高度的工作方式非常相似:它是相对于父元素的高度。例如,两个具有不同高度尺寸但具有相同子元素的父元素会导致子元素具有不同的高度。
填充和边距
当我们查看其他属性(例如 padding
和 margin
)时,父子组合的宽度和高度变得更加有趣。显然,当我们为填充或边距指定百分比值时,它总是相对于父元素的宽度,即使处理垂直边缘也是如此。
一些聪明的设计师利用它来创建宽度和高度相等的框,或者在页面调整大小后保持特定纵横比的框。这对于 视频或图像内容 尤其有用,但也可以以创造性的方式(滥用)。继续,在这个演示中,在可编辑元素中输入您想要的任何内容。无论添加了多少(或多少)内容,该框都会保持比例的高度和宽度。
这种创建纵横比框的技术被称为“填充技巧”。Chris 已经广泛介绍了它。 但是现在,随着 aspect-ratio
属性获得广泛的浏览器支持,我们没有太多理由去使用它。
display: inline 和 inline-block
既然我们已经了解了父元素和子元素尺寸是如何计算的,我们应该检查一下另外两个有趣属性值,它们会影响元素的宽度:min-content
和 max-content
。
这些属性告诉浏览器查看元素的内容以确定其宽度。例如,如果我们有文本:“hello CSS encyclopedia, nice to meet you!”,浏览器将计算该文本在屏幕上占用的空间,并将其用作宽度。
min-content
和 max-content
之间的区别在于浏览器如何执行此计算。对于 max-content
,浏览器假装它有无限的空间,并将所有文本放在一行中,同时测量其宽度。
对于 min-content
,浏览器假装它没有空间,因此它将每个单词/子内联元素放在不同的行中。让我们看看它的实际效果
我们实际上在查看块级元素和内联元素之间的差异时,看到了max-content
的实际应用。还记得吗?内联元素的宽度和高度只与其包含的内容一样大。我们可以通过在其上声明display: inline;
来将大多数元素设置为内联元素。
酷!我们还有另一个武器:display: inline-block;
。它创建一个内联元素,但在盒子模型中增强了块级计算。换句话说,它是一个尊重margin
、width
和height
的内联元素。两全其美!
循环百分比大小
最后一点讲清楚了吗?好吧,希望我不会因为这个让你困惑
在这个例子中,子元素的相对宽度为33%。父元素没有声明宽度。当子元素没有参照对象时,它的计算宽度是如何得到的呢?
为了回答这个问题,我们必须看看浏览器如何计算这个例子中元素的大小。我们没有为父元素定义一个特定的宽度,因此浏览器使用width
的初始值,即auto
。由于父元素的display
设置为inline-block
,auto
的行为类似于max-content
。正如我们所见,max-content
意味着父元素的宽度应该与其内部内容一样宽,即子元素内部的所有内容。
所以,浏览器会查看元素的内容(子元素)来确定其宽度。然而,子元素的宽度也依赖于父元素的宽度!天哪,这太奇怪了!
CSS 盒模型规范将此称为循环百分比大小。我不确定为什么它被称为循环百分比大小,但它详细介绍了浏览器必须执行的复杂数学运算,以(1)确定父元素的宽度,以及(2)将该宽度与子元素的相对宽度进行协调。
这个过程实际上非常酷,一旦你克服了数学难题。浏览器首先在应用声明的值之前,为子元素计算一个临时宽度。浏览器用于子元素的临时宽度是auto
,我们看到它像max-content
一样,它反过来告诉浏览器子元素需要与它包含的内容一样宽。现在,这并不是声明的33%的值。
浏览器使用这个max-content
值来计算父元素的宽度。你会发现,父元素需要至少与它包含的内容一样宽,即子元素中以max-content
表示的所有内容。一旦确定,浏览器就会返回到子元素,并应用在CSS中声明的33%的值。
它看起来像这样

现在我们知道了子元素如何影响其父元素的计算值。
min-
和 max-
属性
M&Ms: 嘿,你可能已经知道以下属性存在
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。
所以,即使我们最初看到两个框在交换width
和max-width
值时表现相同,我们现在知道情况并不总是这样。在这种情况下,引入一个设置为display: inline-block;
的父元素会彻底改变这一切!
min()
、max()
和 clamp()
添加min()
、max()
和 clamp()
是三个有用的 CSS 函数,它们让我们可以以响应的方式定义元素的大小… 无需媒体查询!
min()
: 返回其参数的最小值。参数可以用不同的单位给出,我们甚至可以混合和匹配绝对单位和相对单位,比如min(800px, 100%)
。max()
: 返回其参数的最大值。就像min()
一样,你可以混合和匹配不同的单位。clamp()
: 用于同时执行min
和max
的简写函数:clamp(MIN, VAL, MAX)
解析为max(MIN, min(VAL, MAX))
。换句话说,它会返回VAL
,除非它超过了MIN
和MAX
定义的边界,在这种情况下,它将返回相应的边界值。
像这样。看看我们如何有效地使用单个 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
子元素,该子元素由“非常长的…长的”文本决定。
总结
哇!真是不可思议,看似简单的width
和height
实际上包含了很多内容。当然,我们可以为元素设置明确的width
和height
值,但最终呈现的实际值通常会完全不同。
这就是 CSS 的美妙之处(坦率地说,也是让人沮丧的地方)。它经过精心设计,考虑了多种边缘情况。我的意思是,盒子模型本身的概念既奇妙地复杂,又优雅。我们还能在哪里明确地声明代码中的某件事,并让它以不同的方式解释?width
并不总是宽度。
而且我们还没有触及影响元素尺寸的其他一些因素。现代布局技术,如 CSS Flexbox 和 Grid 引入了轴和轨道线,这些线也决定了元素的渲染尺寸。
作者: Uri Shaked 和 Michal Porag
写得很好。关于这一行有一个说明:
<span>
也会完全忽略宽度和高度以及左右边距属性。我想你是说
top
和bottom
边距。因为inline
元素完全可以有left
和right
边距,这会影响它的兄弟元素。顶部和底部边距属性
这是一个巨大的主题,有太多不同的可能性,我很高兴你写了这篇文章。
我不确定这是否已被涵盖,但我最近遇到了一种情况,绝对定位的元素如果没有设置宽度(例如,设置了最大宽度或元素是内联块并且宽度为自动),它们的宽度将被缩小以防止元素溢出其容器。设置宽度而不是最大宽度可以防止这种情况。
我认为这种情况属于块级格式化上下文阻止其子元素的宽度和边距溢出其父元素的主题。
出色且非常专业的工作,我有一个问题,这些示例适用于视频吗?还是会有所不同?
它应该相对于块级父元素的宽度。
你好,我只是很好奇最后一个例子中 #container2 的父元素宽度是如何计算的。据说它具有子元素的最大内容,但当尝试对 container1 使用 width: max-content 时,似乎 container2 的父元素宽度仍然更长。
#container2 的父元素宽度比 #container1 更长,是因为 #container1 的中间“列”内容由于 . 的存在而宽度较小。如果没有,#container1 会比 #container2 更长,仅仅是因为它有更多内容(即它具有属性“width: 100%;”,而 #container2 没有)。