先记住这个答案
普通的自定义属性在浏览器眼里就是一段字符串。你写 transition: --color 0.3s,浏览器不知道 --color 是颜色还是长度还是别的什么,没法算中间值,所以过渡到一半直接跳变。想要平滑过渡,必须用 @property 把这个属性注册一下,告诉浏览器它的类型。注册之后浏览器知道怎么插值了,比如 <color> 类型会在两个颜色之间做渐变计算,<length> 会在两个数值之间做线性插值。
- 未注册的自定义属性是字符串,过渡时直接跳变
@property注册后指定类型,浏览器才能计算中间值- 注册属性要声明语法、继承和初值
- 注册后可以给渐变背景、阴影等不能直接过渡的属性加动画
未注册时过渡是怎样的
没有注册的自定义属性,过渡效果其实就是一个延迟的跳变。在过渡时间走到 50% 之前保持旧值,50% 的瞬间切到新值。因为浏览器完全不知道 red 到 blue 之间应该经过什么中间值,它没法做任何插值计算。
.box {
--bg: red;
background: var(--bg);
transition: --bg 0.5s;
}
.box:hover {
--bg: blue;
}鼠标移上去的时候,背景不会从红色平滑过渡到蓝色,而是在 0.25 秒处突然变成蓝色。这和 visibility 的过渡行为类似,都是离散型跳变。
很多人写完这段代码看到没有动画效果,就以为自定义属性不支持 transition。其实不是不支持,是浏览器缺少类型信息。
用 @property 注册后就能平滑过渡
@property 是 CSS Houdini 的一部分,用来注册自定义属性并声明它的类型。注册需要三个描述符:syntax 说明值的类型,inherits 说明是否继承,initial-value 给出初始值。三个缺一不可,少写任何一个都会导致注册无效。
@property --bg {
syntax: '<color>';
inherits: false;
initial-value: red;
}
.box {
--bg: red;
background: var(--bg);
transition: --bg 0.5s;
}
.box:hover {
--bg: blue;
}加了 @property 声明之后,浏览器知道 --bg 是颜色类型了,鼠标移上去的时候背景会从红色平滑过渡到蓝色,经过紫色的中间态。
注册的效果是全局的,一个 @property 声明会影响整个文档里所有用到这个属性名的地方。不需要在每个选择器里重复注册。
借助注册属性给渐变加动画
自定义属性过渡最实用的场景是给渐变背景加动画。background: linear-gradient(...) 本身是不支持 transition 的,因为浏览器不知道怎么在两个渐变之间插值。但如果渐变里用了注册过的自定义属性,属性值可以过渡,渐变就会跟着动起来。
@property --start {
syntax: '<color>';
inherits: false;
initial-value: #ff6b6b;
}
.card {
--start: #ff6b6b;
background: linear-gradient(135deg, var(--start), #4ecdc4);
transition: --start 0.6s;
}
.card:hover {
--start: #a855f7;
}鼠标移到卡片上时,渐变的起始色从红色平滑过渡到紫色,终止色保持不变。以前实现这个效果需要用两个伪元素叠在一起做 opacity 动画,现在用 @property 几行就够了。
同样的思路可以用在圆锥渐变做的饼图、径向渐变做的光晕效果上。只要把想要动画的那个值抽成注册过的自定义属性,就能用 transition 或 @keyframes 驱动它变化。
syntax 支持哪些类型
syntax 的值是一个字符串,描述属性接受什么类型。常用的有 <color>(颜色)、<length>(长度,比如 px、rem)、<number>(纯数字)、<percentage>(百分比)、<angle>(角度)、<length-percentage>(长度或百分比)。
你也可以用 | 组合多个类型,比如 '<length> | <percentage>'。如果设成 '*',等于没有类型限制,和不注册一样没法过渡。
设了 syntax 之后,赋给属性的值会被类型检查。如果写了不合规的值(比如给 <color> 类型赋了 10px),这条声明不会生效,属性保留之前的值或回退到 initial-value。
容易答错的地方
- 以为
transition写了就会过渡 - 不注册的自定义属性写
transition不会报错,但不会有平滑效果。浏览器只是在 50% 处做了一次离散跳变。必须用@property注册并指定类型才能真正插值。 - 漏了
initial-value @property的三个描述符必须全部写。漏了initial-value整个注册无效,浏览器会忽略这条@property,属性回到未注册状态,过渡又变成跳变。
面试官还会怎么问?
除了 CSS 的 @property,还有别的注册方式吗?
JavaScript 里可以用 CSS.registerProperty() 达到同样的效果。参数是一个对象,包含 name、syntax、inherits、initialValue。好处是可以动态注册,坏处是注册后不能修改或取消,页面生命周期内持续有效。
@keyframes 动画里自定义属性也需要注册吗?
需要。@keyframes 和 transition 一样,都依赖浏览器做值的插值计算。未注册的自定义属性在关键帧之间也是离散跳变,注册之后才能平滑过渡。
@property 的浏览器兼容性怎么样?
Chrome 85+、Edge 85+、Safari 15.4+、Firefox 128+ 都支持。如果要兼容不支持的浏览器,可以写一个不依赖 @property 的回退样式,让动画降级为跳变,功能不受影响,只是没有平滑效果。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。