前端知库

interpolate-size 和 calc-size() 怎样实现 auto 高度动画?

解释 intrinsic size 关键字为什么过去不能直接参与 CSS 过渡,说明 interpolate-size、calc-size() 与展开面板的配合方式,以及布局成本、可访问状态和兼容回退。

参考答案

简答

  • auto 不是确定数值,过去 height: 0 到 height: auto 无法插值,只能用 JavaScript 读 scrollHeight 或猜一个 max-height。
  • interpolate-size: allow-keywords 允许数值和 auto 等尺寸关键字之间插值,属性会继承,可以放在根元素或单个组件上。
  • calc-size(auto, size + 1rem) 可以在自然尺寸上做计算;普通 calc(auto + 1rem) 不成立。
  • 它只处理尺寸,hidden、inert、ARIA 和焦点仍要由组件同步;高度动画会触发布局。
CSS
:root {
  interpolate-size: allow-keywords;
}

details::details-content {
  block-size: 0;
  overflow: clip;
  transition:
    block-size 260ms ease,
    content-visibility 260ms allow-discrete;
}

details[open]::details-content {
  block-size: auto;
}

过渡写在 ::details-content 上,而不是 <details> 本身,否则 <summary> 也会被裁掉。

calc-size() 和高度上限

CSS
.accordion-panel {
  interpolate-size: allow-keywords;
  height: 0;
  max-block-size: 60vh;
  overflow: clip;
  transition: height 220ms ease;
}

.accordion-panel[data-expanded='true'] {
  height: auto;
  overflow: auto;
}

.note[data-expanded='true'] {
  height: calc-size(auto, size + 1rem);
}

自然高度负责展开,超长内容由 max-block-size 加内部滚动处理。

展开状态不能只靠视觉

按钮要用 aria-expanded 和 aria-controls 表达状态。只用 height: 0 收起时,内容仍可能被读屏读到、被 Tab 聚焦,需要配合 inert;用 hidden 则要结合 @starting-style 和离散过渡才能有动画(见“@starting-style 和离散属性过渡解决了什么问题?”)。

布局成本

高度变化会推动后续内容,每帧都要重新布局,成本高于 transform 动画。只对用户主动展开的局部内容使用,限制最大高度,给图片和异步内容预留尺寸,并为 prefers-reduced-motion: reduce 提供无动画路径。

旧方案为什么不等价

max-height: 40rem 的回退动画时长与真实内容不成比例,超过上限的内容会被截掉。JavaScript 读写 scrollHeight 要处理字体变化、窗口调整、取消动画和卸载,还可能造成强制同步布局,应封装在组件里。

兼容回退

CSS
.panel {
  height: 0;
  overflow: hidden;
}

/* 展开状态写在 @supports 外:不支持的浏览器照样能展开,只是没有动画 */
.panel[data-open='true'] {
  height: auto;
}

@supports (interpolate-size: allow-keywords) {
  .panel {
    interpolate-size: allow-keywords;
    transition: height 220ms ease;
  }
}

能力检测通过也不代表所有关键字组合都支持。验收覆盖空内容、超长内容、异步内容、快速重复点击、键盘与读屏、文字放大,以及不支持的浏览器。