进阶:让「本来不能动」的东西动起来
有两类东西默认无法动画:其一是自定义属性(CSS 变量)被当作字符串,只能离散跳变;其二是 height: auto / fit-content 这类关键字尺寸无法插值。@property 与 interpolate-size / calc-size() 分别对应这两类问题的解法。
1 · @property——给自定义属性「上类型」,它才能被插值
@property 用 syntax 给自定义属性声明类型(<angle> / <number> / <color> …)。一旦有了类型,浏览器就知道怎么在两个值之间插值——于是能动画 transform 做不到的东西:渐变角度、单个颜色、用于 counter 的数字。@property
本身(类型语法、initial-value、IACVT)在 CSS 自定义属性系列的 @property 页有更完整的讲解,本页只用它服务于动画。
@property --angle { syntax: "<angle>"; inherits: false; initial-value: 0deg; }
.box {
background: conic-gradient(from var(--angle), red, blue, red);
animation: spin 3s linear infinite;
}
@keyframes spin { to { --angle: 360deg; } } /* 渐变自己转起来 */
左边转的是渐变本身(不是 transform 旋转盒子);右边是把一个 <integer> 从 0 动画到 100、再用 counter() 显示成数字——没有 @property 注册类型,这两个都只会在首尾瞬间跳变。
2 · 动画到 auto / fit-content / stretch——关键字尺寸怎么补间
经典问题:展开折叠面板想从 height: 0 平滑过渡到 height: auto,但 auto 不可插值,过去只能用 max-height 等近似手段。下面三种方案各管一类场景:interpolate-size 是标准做法、grid 行技巧是跨浏览器的现成替代、动画到 stretch 则是另一个机制。
2.1 · 标准做法:interpolate-size / calc-size()
在祖先上写 interpolate-size: allow-keywords(或用 calc-size(auto, size)),就能让长度与关键字尺寸之间真正过渡。可插值清单:auto / min-content / max-content / fit-content / content。目前仅 Chromium 系支持。
.acc { interpolate-size: allow-keywords; }
.acc-body { height: 0; overflow: hidden; transition: height .5s ease; }
.acc.open .acc-body { height: auto; } /* 真正过渡到 auto */
2.2 · 跨浏览器版:grid
想现在就在 Firefox / Safari 里也能动画到 auto,经典做法是把内容包进一个 grid:行轨道 0fr(塌成 0)过渡到 1fr(撑到内容自然高度),内层再 overflow: hidden 裁掉溢出。fr 是可插值的 <number>,所以补间天然顺滑。关键:不要拿 [inert] 当展开开关——inert 会让整棵子树不可聚焦、不可点击,出于无障碍考虑应该用 class 或 aria-expanded 切换。
.body { display: grid; grid-template-rows: 0fr; transition: grid-template-rows .5s; }
.body > .inner { overflow: hidden; }
.open .body { grid-template-rows: 1fr; } /* 1fr = 内容真实高度 */
2.3 · 动画到 stretch(填满父级):用 flex-grow
注意 interpolate-size 的可插值清单不含 stretch(即旧的 -webkit-fill-available),所以「从 0 长到填满父级」无法靠它。真正可插值的是 flex-grow(一个 <number>):在 flex 容器里把子项 flex-grow: 0 → 1,它就平滑填满父级剩余空间——效果等同动画到 stretch。(把上面 grid 容器设成定高、行写
1fr 也能达到同样的填满效果。)
.col { display: flex; flex-direction: column; height: 180px; }
.col .grow { flex-grow: 0; overflow: hidden; transition: flex-grow .5s; }
.col.open .grow { flex-grow: 1; } /* 撑满剩余 = stretch */
想从 display: none 出场、再长到 stretch?那是另一个机制:@starting-style + transition-behavior: allow-discrete,见 none → block 也能动起来。