表单元素的高度我算了八年,现在 CSS 自己会量了——`calc-size()` 把内在尺寸这件事彻底变了

表单元素的高度我算了八年,现在 CSS 自己会量了——calc-size() 把内在尺寸这件事彻底变了

以前给 select 下拉框设高度,基本靠猜。你不知道用户选了几项,不知道每个选项有多高,CSS 里没有一种方式是”根据内容自动撑开”的。所以大家习惯用 JS 算 scrollHeight,绑 resize 事件监听,内容一变就重新算一遍。这不是大问题,但它是那种”你每天都在忍受但从来没细想过”的类型。

Chrome 129 把这个坑填上了。calc-size() 是 CSS 内在尺寸模块的一个新函数,它允许你在属性的值里对”内在尺寸”做运算——这件事以前在 CSS 里是不可能的。

核心语法就两件事

height: calc-size(auto, size + 20px);

第一个参数是内在尺寸基础,可以是 automin-contentmax-contentfit-content。第二个参数是一个表达式,用 size 关键字代表第一个参数传入的那个内在尺寸值,在上面做加减值、乘除法、或者套 round()max() 这类函数。

上面这行代码的意思是:高度等于 auto 的内在尺寸,再加 20px。以前你得写 height: auto; padding: 10px 0; 之类靠凑的方式来模拟,现在一行搞定,而且结果是一个真正的”随内容变化的内在尺寸”。

下拉框高度终于可控了

select 元素以前高度写死很难看,内容一多选项就显示不全:

/* 以前 */
select {
  height: 40px; /* 写死了,内容多了就截 */
}

/* 现在 */
select {
  field-sizing: content;
  height: calc-size(auto, size + 8px);
  min-height: 36px;
  max-height: 200px;
}

field-sizing: content 让 select 的宽高都跟着内容走,calc-size(auto, size + 8px) 在这个基础上再加 8px 的垂直内边距。min-height 防止光标高度太窄,max-height 防止选项过多时撑破布局。这套组合以前要靠 JS 绑 MutationObserver 才能做到,现在纯 CSS。

内在尺寸也能做动画了

calc-size() 最大的价值是打通了内在尺寸和动画系统的壁垒。以前你想做一个从 0 扩展到完整高度的展开动画,height: auto 是不能做过渡的——浏览器不知道 auto 到底是多少像素。只能用 max-height 凑一个大值,但这样会有延迟关闭的”慢慢收”的问题。

.expander {
  height: 0;
  overflow: hidden;
  transition: height 300ms ease;
}
.expander.open {
  height: calc-size(auto, size); /* 从 0 过渡到 auto,真正的内在尺寸 */
}

height: calc-size(auto, size) 和普通 height: auto 视觉效果完全一样,但这次浏览器知道具体的像素值是多少——因为 auto 在这个上下文中已经被具体化为内容实际的内在尺寸了。所以 0→auto 的过渡动画第一次在 CSS 里原生支持,不需要任何 JS 辅助。

MDN 的说明里特别提到,如果只是想让元素从 0 宽高过渡到 auto,interpolate-size 属性比 calc-size() 更简单。但如果既要过渡到 auto、又要同时加一个计算量(比如加 padding),calc-size() 是唯一解。

性能账:删掉的不只是几行 JS

用 JS 测量 scrollHeight 的方案有一个隐藏成本:每次内容变化你都要触发一次 layout 计算,然后设置属性,浏览器再 layout 一次。这在内容频繁变化的场景里是把主线程反复往复拖一遍的过程。

calc-size() 是声明式的。浏览器在 paint 阶段一次性把内在尺寸和计算结果算出来,不需要你手动触发,不需要事件监听,也不需要 JS 和 CSS 之间反复通信。MDN 原文说得很清楚:避免动画 box model 属性,以减少 layout 事件和对性能的影响calc-size() 正是让内在尺寸能够被浏览器原生处理而不走 JS 的那条路。

Baseline 状态

calc-size() 随 Chrome 129 在 2024 年第三季度进入 Baseline,其他主流浏览器也在逐步跟进。如果你现在要写生产代码,需要查一下 caniuse 的实时覆盖率——但基础用法(auto + size)在 Chrome 下已经完全可靠。

下一步怎么动手

如果你现在表单里还有 element.scrollHeight 相关的 JS 代码,可以分两步替换:

第一步,只保留 field-sizing: content,去掉 JS 的宽度计算,看现有布局是否能接受纯内在尺寸。

第二步,把需要加固定 padding、margin、或做展开动画的地方,换成 calc-size(auto, size ± 固定值),对比一下渲染结果和性能指标。

如果你的项目对兼容性要求高,渐进增强方案是:正常写 calc-size(),对不支持的浏览器用 @supports 回退到固定高度或已有的 JS 方案——但至少对 Chrome 用户来说,这部分 JS 可以直接删掉了。

评论区

0 条评论

登录后可评论。

阿速·性能优化 695 阅读