计算值:不只是表面上的

Avatar of Jemimah Omodior
Jemimah Omodior

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

浏览器开发者工具对于我们前端开发人员来说必不可少。在这篇文章中,我们将深入了解 计算 标签,它是开发者工具面板的一个小角落,却展示了我们的大发现,例如如何解析相对 CSS 值。我们还将了解继承是如何融入浏览器样式计算过程的。

Screenshot of DevTools window for Chrome in dark mode.
“计算”标签通常位于开发者工具界面的右侧面板,就像这里在 Chrome 中显示的那样。

“计算”标签中的内容很重要,因为它 **展示了浏览器在渲染网站时实际使用的值。** 如果某个元素的样式与您预期的不符,查看其计算值可以帮助您理解原因。

如果您更习惯于使用“样式”标签(在 Firefox 中称为“规则”),您可能想知道它与“计算”标签有何不同。我的意思是,它们都显示应用于元素的样式。答案是:“计算”标签显示的是按字母顺序排列的 *解析* 样式列表,包括样式表中声明的样式、从继承获得的样式以及 浏览器默认值

Screenshot of Chrome DevTools in dark mode. DOM elements are on the left and the Computed Properties information is on the right.
“计算”标签会选择一个元素 (1),并显示一个已渲染的 CSS 属性列表 (2),允许展开每个属性 (3) 以显示继承值的级联以及当前使用的实际计算值 (4)。

另一方面,“样式”标签会显示所选元素的精确规则集,就像它们被写入的那样。因此,虽然“样式”标签可能会显示类似 .subhead {font-size: 75%} 的内容,但“计算”标签将显示实际的字体大小,或者 70% 当前解析成的值。例如,如上所示,渲染文本的实际字体大小为 13.2px

Screenshot of Chrome DevTools in dark mode. DOM elements are on the left and the Styles information is on the right.
“样式”标签会选择一个元素 (1),并显示在样式表中明确声明的规则集 (2),以及级联中包含的其他相关规则集 (3),包括来自其他样式表的规则集 (4)。请注意,被覆盖的值是如何被划掉,表示另一个属性具有优先级。

接下来,让我们简要回顾一下继承和级联的概念,这两个概念是“计算”标签中计算值的形成过程中非常重要的部分。

继承和级联的速成课程


CSS 代表层叠样式表,而第一个词“层叠”对于理解 CSS 非常重要——级联的行为方式是理解 CSS 的关键。

MDN

级联之所以值得关注,是因为它是 CSS 中的“C”。它是一种机制,用于解决文档中 不同来源 的样式声明之间存在的冲突。

例如,想象一个样式表两次定义了 div 的宽度

div {
  width: 65vw;
}


/* Somewhere, further down */
div {
  width: 85vw;
}

在这个特定例子中,第二个宽度会胜出,因为它最后声明。第一个宽度 仍然可以通过 !important 获胜,但这在技术上是通过蛮力打破级联。这里的重点是级联算法会确定哪些样式应用于每个元素,并按 预定的顺序 对它们进行优先级排序,以确定最终值。

级联应用于明确设置的属性,无论是由浏览器、网页开发者还是用户设置。当级联的输出为空时,继承就会发挥作用。当这种情况发生时,元素父级属性的计算值将被拉入作为其自身的属性值。例如,如果您为元素指定了颜色,那么如果您没有指定其子元素的颜色,所有子元素将继承该颜色。

在深入研究之前,我们应该熟悉与继承相关的四个关键属性值。我们将在整篇文章中使用它们。

initial

在 DOM 树的最高级别是 <html> 元素的 HTML 文档中,当我们在元素上使用 initial 关键字时,例如...

...该元素的文本颜色为黑色,即使 body 元素设置为 greendiv 选择器的特异性更高,这是一个问题,但是我们感兴趣的是为什么 initial 翻译为黑色。

简单来说,这个关键字根据属性定义表(在 CSS 规范中)设置属性的默认值。在本例中,黑色恰好是浏览器对 initial 颜色值的实现。

我在文章的最后提到了,您可以通过查看 MDN 上的属性页面来了解属性是否默认继承。当然,您也可以通过这种方式找到任何属性的初始值。

inherit

对于非继承属性,此关键字强制继承。在以下示例中,<body> 元素有一个实线红色边框。border 属性默认不会被继承,但是我们可以通过在 div 的 border 属性上使用 inherit 关键字来告诉它继承 <body> 元素上声明的相同红色边框

unset

unset 如果属性是继承的,则会解析为继承值。否则,将使用 initial 值。这基本上意味着 unset 会根据属性是否被继承来重置属性。这是一个演示,它切换 unset 以显示它对具有不同特异性级别的元素的影响。

revert

如果元素上没有设置任何 CSS 属性,它会获得任何样式吗?当然。它使用浏览器的默认样式。

例如,span 元素的 display 属性的初始值为 inline,但我们可以在样式表中将其指定为 block。使用以下演示中的按钮在 span 元素的 displaycolor 属性上切换 revert

该 span 正确地恢复为内联元素,但是等等!您是否注意到 span 的颜色变为绿色,而不是浏览器的默认黑色值?这是因为 revert 允许继承。它会一直回溯到浏览器的默认值以设置颜色,但由于我们已在 <body> 元素上明确设置了绿色,因此该颜色会被继承。

在开发者工具中查找计算值

这里我们开始讨论开发者工具中的计算值。就像属性的默认值一样,CSS 属性的计算值是由 CSS 规范中该属性的定义表决定的。 这是 height 属性的定义表。

假设我们在 CSS 中使用相对长度,例如 10em 或 70% 或 5vw 之一。由于这些长度是相对于某些东西的——字体大小或视窗——它们需要解析为像素绝对值。例如,一个宽度为 10% 的元素如果视窗宽度为 1000px,则其计算宽度可能为 100px,但当视窗宽度发生变化时,其计算宽度可能完全不同。

Screenshot of Chrome with DevTools open in dark mode on the right. CSS-Tricks is the open site, the elements tab is open in the center, and the Computed Properties values are open on the left.
一个按钮 (1) 是开发者工具 (2) 中当前选定的元素。按钮的声明宽度为 100% (3),当视窗处于这种状态时,其计算宽度为 392px (4)。

这些值在 DOM 被修改时计算,这个过程称为 **计算样式计算**。这使浏览器知道要将哪些样式应用于每个页面元素。

样式计算包含多个步骤,涉及多个值。这些步骤记录在CSS 级联和继承级别 4 规范中,它们都会影响我们在计算选项卡中看到的最终值。让我们接下来看看这些内容。

值以及它们如何被处理

为样式计算过程定义的值包括**声明值**、**指定值**、**级联值**、**计算值**、**使用值**和**实际值**。谁知道有这么多呢,对吧?

声明值

一个声明值是任何应用于元素的属性声明。浏览器根据以下几个标准识别这些声明,包括

  • 声明位于应用于当前文档的样式表中
  • 样式声明中存在匹配的选择器
  • 样式声明包含有效的语法(即有效的属性名称和值)

以下面的 HTML 为例

<main>
  <p>It's not denial. I'm just selective about the reality I accept.</p>
</main>

以下是应用于文本font-size的声明值

main {
  font-size: 1.2em; /* this would apply if the paragraph element wasn't targeted specifically, and even then, as an inherited value, not "declared value" */
}


main > p {
  font-size: 1.5em; /* declared value */
}

级联值

应用于元素的所有声明值列表根据以下内容进行优先级排序,以返回单个值

  • 声明的来源(来自浏览器、开发人员还是其他来源?)
  • 声明是否标记为“!important”
  • 规则的特定性(例如,span {}section span {}
  • 出现顺序(例如,如果多个声明适用,则使用最后一个声明)

换句话说,**级联值**是“获胜”的声明。如果级联没有产生获胜的声明值,那么就没有级联值。

main > p  {
  font-size: 1.2em;
}


main > .product-description { /* the same paragraph targeted in the previous rule */
  font-size: 1.2em; /* cascaded value based on both specificity and document order, ignoring all other considerations such as origin */
}

指定值

如前所述,级联的输出可能为空。但是,仍然需要通过其他方式找到一个值。

现在,假设我们没有为元素上的特定属性声明值,但为其父元素声明了值。这通常是我们有意为之,因为没有必要在多个地方设置相同的值。在这种情况下,将使用父元素的继承值。这被称为指定值

在许多情况下,级联值也是指定值。但是,如果不存在级联值,并且相关属性是继承的(无论是默认继承还是使用inherit关键字),它也可能是一个继承值。如果属性不是继承的,则指定值为属性的初始值,如前所述,也可以使用initial关键字显式设置该值。

总之,指定值是我们打算在元素上使用的值,无论是否在该元素上显式声明它。这有点模糊,因为如果样式表中没有声明任何内容,浏览器的默认值也会成为指定值。

/* Browser default = 16px */


main > p {
  /* no declared value for font-size for the paragraph element and all its ancestors */
}

计算值

之前我们简要讨论了如何将相对值解析为其像素绝对等效值。如前所述,此过程是预先确定的。例如,属性定义表包含“计算值”字段,详细说明了如何一般地解析指定值。

Screenshot of the specifications section of the color property, taken from the MDN docs. The "Computed value" field is highlighted.
MDN 文档中颜色属性的规范部分。

在以下示例中,我们使用的是em,这是一个相对单位。在这里,应用属性的元素在渲染时使用的最终值不是声明值中看到的固定数字,而是需要根据一些因素计算得出的值。

main {
  font-size: 1.2em;
}


main > p {
  font-size: 1.5em; /* declared value */
}

段落元素的font-size设置为 1.5em,它相对于main元素的font-size值 1.2em。如果mainbody元素的直接子元素——并且上面没有进行额外的font-size声明,例如使用:root选择器——我们可以假设段落的font-size计算将遵循以下近似过程

Browser_Default_FontSize = 16px;
Calculated_FontSize_For_Main = 1.2 * Browser_Default_FontSize; // 19.2px
Calculated_FontSize_For_Paragraph = 1.5 * Calculated_FontSize_For_Main; // 28.8px

该 28.8px 是计算值以下是一个演示

打开开发者工具,查看计算选项卡中的计算字体大小。

Screenshot of Chrome DevTools open to the Element view with Computed Properties open.
main元素的声明font-size为 1.2em,计算为 19.2px。
Screenshot of Chrome DevTools open to the Element view with Computed Properties open.
段落元素的声明font-size为 1.5em,计算为 28.8px。

假设我们使用rem单位代替

html {
  font-size: 1.2em;
}


main {
  font-size: 1.5rem;
}


div {
  font-size: 1.7rem;
}

rem单位的计算值基于根 HTML 元素的font-size,这意味着计算会略有不同。在本例中,我们在 HTML 元素上也使用了一个相对单位,因此浏览器的默认font-size值用于计算我们将用来解析所有rem值的基准font-size

Browser_Default_FontSize = 16px
Root_FontSize = 1.2 * Browser_Default_FontSize; // 19.2px
Calculated_FontSize_For_Main = 1.5 * Root_FontSize; // 28.8px
Calculated_FontSize_For_Div = 1.7 * Root_FontSize; // 32.64px

再次打开开发者工具查看此演示

Browser_Default_FontSize的值 16px 通常由浏览器使用,但这会因浏览器而异。要查看当前默认值,请在开发者工具中选择<html>元素,并查看显示的font-size。请注意,如果为根元素显式设置了一个值,就像我们的示例一样,您可能需要在规则选项卡中将其关闭。接下来,在计算选项卡中打开“显示所有”或“浏览器样式”(Firefox)复选框,以查看默认值。

在继承期间,计算值会从父元素传递到子元素。此计算过程会考虑我们之前讨论的四个继承控制关键字。一般来说,相对值会变为绝对值(即 1rem 变为 16px)。这也是相对 URL 变成绝对路径的地方,以及bolderfont-weight属性的值)之类的关键字被解析的地方。您可以在文档中查看此过程的一些示例

使用值

**使用值**是在对计算值完成所有计算后的最终结果。在这里,所有相对值都会变为绝对值。这个使用值将(暂时)应用于页面布局。您可能想知道为什么需要进行进一步的计算。难道之前的步骤在处理指定值以生成计算值时没有处理完吗?

事实是:一些相对值只有在此时才会解析为像素绝对值。例如,百分比指定的宽度可能需要页面布局才能解析。但是,在许多情况下,计算值最终也会成为使用值。

请注意,在某些情况下,使用值可能不存在。根据CSS 级联和继承级别 4 规范

…如果属性不适用于元素,则它没有使用值;因此,例如,flex属性在不是弹性项目的元素上没有使用值

实际值

有时,浏览器无法立即应用使用值,需要进行调整。这个调整后的值被称为实际值。想想那些需要根据可用字体调整字体大小的情况,或者浏览器在渲染时只能使用整数值,需要近似非整数值的情况。

浏览器样式计算中的继承

回顾一下,继承控制着当一个属性没有显式设置时,应用于元素的值。对于继承属性,此值取自父元素计算的值,对于非继承属性,则设置该属性的初始值(当指定关键字initial时使用的值)。

我们之前讨论过“计算值”的存在,但我们需要澄清一些事情。我们讨论了计算值作为参与样式解析过程的一种值类型,但“计算值”也是浏览器为页面样式计算的值的通用术语。您通常可以通过周围的上下文来理解我们指的是哪种类型。

只有计算值才能被继承属性访问。 像 477px 这样的绝对像素值,像 3 这样的数字,或像 left 这样的值(例如text-align: left)都已准备好进行继承过程。 像 85% 这样的百分比值则不行。 当我们为属性指定相对值时,必须计算最终(即“使用”)值。 百分比值或其他相对值将乘以参考大小(例如font-size)或值(例如设备视窗的宽度)。 因此,属性的最终值可能只是声明的值,也可能需要进一步处理才能使用。

您可能已经注意到,浏览器“计算”选项卡中显示的值不一定是我们之前讨论过的计算值(如计算值与指定值或使用值相比)。 相反,显示的值与getComputedStyle() 函数返回的值相同。 此函数返回一个值,该值根据属性的不同,将是计算值或使用值。

现在,让我们看一些例子。

颜色继承

main {
  color: blue;
}

/* The color will inherit anyway, but we can be explicit too: */
main > p {
  color: inherit;
}

主元素的color属性计算出的值为蓝色。 由于color默认情况下是继承的,因此我们实际上不需要为段落子元素使用color: inherit,因为它最终也会变成蓝色。 但它有助于说明这一点。

颜色值会经历自己的解析过程以成为使用值。

字体大小继承

main {
  font-size: 1.2em;
}

main > p {
  /* No styles specified */
}

正如我们在前面关于值及其处理方式的部分中看到的那样,我们用于font-size的相对值将计算为绝对值,然后被段落元素继承,即使我们没有显式声明它(同样,font-size默认情况下是继承的)。 如果我们之前通过全局段落元素选择器设置了样式,那么段落可能会通过级联获得一些额外的样式。 任何可能被继承的属性值都将被继承,而一些级联和继承没有产生值的属性将被设置为其初始值。

百分比指定的字体大小继承

body {
  font-size: 18px;
}

main {
  font-size: 80%;
}

main > p {
  /* No styles specified */
}

与之前的例子类似,<main> 元素的font-size将在准备继承之前被绝对化,段落将继承一个font-size,它是正文的 18px 值的 80%,即 14.4px。

强制继承和布局后计算

计算值通常会尽可能在没有布局的情况下解析指定值,但如前所述,一些值只能在布局后解析,例如百分比指定的width值。 虽然width不是继承属性,但我们可以强制继承来说明布局前和布局后的样式解析。

这是一个人为的例子,但我们正在做的是通过将元素的display属性设置为none来将其从页面布局中移除。 在我们的标记中,有两个 div 继承了它们父元素<section>width,即 50%。 在 DevTools 的“计算”选项卡中,第一个 div 的计算width是绝对的,已经解析为像素值(对我来说是 243.75px)。 另一方面,使用display: none从布局中移除的第二个 div 的宽度仍然是 50%。

我们可以想象,父<section>元素的指定值和计算值为 50%(布局前),使用值如“计算”选项卡所示,对我来说是 487.5px(布局后)。 此值被子 div 继承时减半(包含块的 50%)。

这些值必须在浏览器视窗宽度发生变化时重新计算。 因此,百分比指定的数值将变为百分比计算值,最终变为像素使用值。

默认继承的属性

如何知道一个属性是否默认继承? 在 MDN 文档中,每个 CSS 属性都包含一个规范部分,其中提供了一些额外的细节,包括该属性是否被继承。 以下是color属性的样子

Screenshot of the specifications section of the color property, taken from the MDN docs. The "Inherited" field is highlighted.
MDN 文档中颜色属性的规范部分。

哪些属性默认继承,哪些属性没有继承,很大程度上取决于常识。

MDN

另一个参考选项是W3C 规范的属性部分。 另一个是这个 StackOverflow 线程,它在撰写本文时可能并不详尽。

以下是一些默认继承的属性的例子

不继承的属性的例子(但您可以使用inherit关键字强制它们继承)


希望这能让您对浏览器如何计算样式以及如何在 DevTools 中引用它们有一个清晰的认识。 如您所见,后台有很多值在起作用。 拥有这些背景知识对于帮助您解决工作中的问题以及加深您对我们所知道的称为 CSS 的奇妙语言的理解大有帮助。

进一步阅读