◆

CSS 属性计算值(computed value)详解

2023-02-02 01:370

什么是 css 属性的计算值

一个 CSS 属性的计算值是指这个属性在由父类转向子类的继承中的值。它通过指定值计算出来:

  • 计算值所需要的计算通常包括将相对值转换成绝对值(如 em 单位或百分比)

场景

  • 如一个元素的属性值为 font-size:16px 和 padding-top:2em, 则 padding-top 的计算值为 32px (字体大小的 2 倍).
  • 或者没有单位的 line-height 属性值就是计算值。

完整的值处理流程

计算值只是 CSS 规范定义的值处理链条中的一环。整条链是这样的:

  1. 声明值(declared value):所有匹配到该元素的规则里,为这个属性声明的每一个值。可能有多个,互相冲突。
  2. 层叠值(cascaded value):按重要性、来源、选择器优先级、书写顺序排完序后胜出的那一个。
  3. 指定值(specified value):层叠值存在就用它;不存在但属性可继承,就取父元素的计算值;否则取初始值。
  4. 计算值(computed value):把相对单位、相对关键字尽可能转成绝对形式。这一步不需要知道布局结果。
  5. 使用值(used value):布局算完之后才能确定的最终值,比如百分比宽度换算成具体像素。
  6. 实际值(actual value):使用值经过设备取整、字体可用性等约束后真正落到屏幕上的值。

理清这个链条能解释很多看起来不合直觉的现象。核心分界线在第 4 步和第 5 步之间:计算值阶段拿不到布局信息。

为什么百分比不在计算值阶段解析

em 依赖的是 font-size,而 font-size 在同一元素上是先算完的,所以 2em 可以在计算值阶段直接变成 32px。但 width: 50% 依赖的是父元素的内容宽度,那是布局阶段才确定的东西,所以它的计算值仍然是 50% 这个百分比本身,只有到使用值阶段才变成具体像素。

这可以直接验证:

<div id="parent" style="width: 400px">
  <div id="child" style="width: 50%; padding-top: 2em; font-size: 16px"></div>
</div>

<script>
  const cs = getComputedStyle(document.getElementById('child'));
  // padding-top 在计算值阶段就绝对化了
  console.log(cs.paddingTop); // "32px"
  // width 理论上计算值是 50%,但 getComputedStyle 返回的是解析后的值
  console.log(cs.width); // "200px"
</script>

getComputedStyle 返回的其实不全是计算值

这是个长期的命名混淆。getComputedStyle() 的名字里写着 computed,但规范要求它对布局相关属性返回使用值(resolved value),对其余属性返回计算值。所以上面 width 拿到的是 200px 而不是 50%。

哪些属性返回使用值?主要是 width、height、top / right / bottom / left、margin-*、padding-* 这一批盒模型属性。其余的(color、font-size、line-height、display、z-index 等)返回计算值。

顺带说个更实际的坑:元素或其祖先 display: none 时,布局压根没跑,此时 getComputedStyle 返回的就是计算值本身——width 会拿到 "50%" 甚至 "auto"。所以「读隐藏元素的宽度得到 auto」不是 bug。

继承传递的是计算值

标题里那句「由父类转向子类的继承中的值」说的就是这件事,值得展开:子元素继承父元素时,拿的是父元素的计算值,不是声明值,也不是使用值。

font-size 是最直观的例子。父元素 font-size: 2em、祖父 16px,父元素的计算值是 32px,子元素继承到的就是 32px,不会再乘一次 2em。

line-height 恰恰相反,这也是原文提到「没有单位的 line-height 就是计算值」的原因所在。带单位的 line-height 在计算值阶段就绝对化了,继承下去是固定像素;不带单位的保持为数字,继承下去后各自乘以自己的 font-size:

.a {
  font-size: 16px;
  line-height: 1.5; /* 计算值就是数字 1.5 */
}
.a .big {
  font-size: 32px;
  /* 继承到 1.5,行高 = 32 * 1.5 = 48px,跟着字号缩放 */
}

.b {
  font-size: 16px;
  line-height: 1.5em; /* 计算值是 24px */
}
.b .big {
  font-size: 32px;
  /* 继承到 24px,行高被钉死,32px 的字挤在 24px 的行里 */
}

所以设置全局 line-height 一律用无单位数字,这不是风格偏好,是继承机制决定的。150% 和 1.5em 在这件事上是一样的坑,都会在父元素上先绝对化。

几个特殊的计算值规则

inherit / initial / unset 在计算值阶段就被消化掉了。 它们只是取值指令,计算完之后属性上留下的是真实的值。

CSS 自定义属性(--x)几乎不做计算。 它们的计算值是把 token 流原样保留(只做 var() 替换),因为浏览器不知道这个变量将来会被用在什么属性上,无法确定该按什么规则绝对化。这就是为什么 --gap: 10px 之后 --gap 不能直接参与 calc 之外的类型检查,也是 @property 存在的理由——声明了类型的自定义属性才能正常参与计算和插值动画。

部分关键字会在计算值阶段被替换。 例如 float: left 的元素上,display: inline 的计算值会被改成 block;绝对定位元素上同样有这种「盒类型修正」。这解释了为什么给浮动元素写 display: inline 没有效果。

💬留言板0

还没有留言,来发表第一条吧~

PAGE LOAD / LIVECSS 属性计算值(computed value)详解加载中