@property 注册自定义属性,CSS动画从此能自定义
CSS 里有个让人又爱又恨的东西:自定义属性(也就是我们常说的 CSS 变量)。爱它是因为它让样式第一次有了"参数"的概念,改一个值,全站跟着变;恨它是因为——它动不了。你没法让一个 `--progress` 从 0 平滑过渡到 1,`transition` 对它毫无反应,`@keyframes` 里写它也只会在 50% 的地方硬生生跳一下。
为什么?因为在浏览器眼里,未注册的自定义属性只是一串「未知的 token」,它不知道这是一个长度、一个角度还是一个颜色,自然也就不知道该怎么在 0 和 1 之间插值。而 `@property` 要解决的,正是这件事。
给变量发一张身份证
`@property` 是 CSS 的一个 at-rule,用来向浏览器注册一个自定义属性,并声明它的类型:
@property --angle {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
三个描述符各有分工:
- `syntax`:告诉浏览器这个值是什么类型,比如 `<angle>`、`<color>`、`<length-percentage>`、`<number>`。写 `"*"` 表示任意 token 流,但那样就等于没注册,不能插值。
- `inherits`:是否继承给子元素。默认自定义属性是继承的,但做动画时我们通常希望它别到处乱窜,所以经常设成 `false`。
- `initial-value`:初始值。只要 `syntax` 不是 `"*"`,这一项就是必填的,而且必须能被该类型解析,否则整条规则会被忽略。
三者齐备之后,浏览器就真正"认识"这个属性了。它有了合法值校验、有了类型、有了插值规则——也就有了动画能力。
让渐变自己转起来
最能体现威力的例子,是用 `conic-gradient` 做一个旋转的彩色光晕:
@property --angle {
syntax: "<angle>";
inherits: false;
initial-value: 0deg;
}
.glow {
background: conic-gradient(
from var(--angle),
#ff5f6d, #ffc371, #47cf73, #5b8cff, #ff5f6d
);
animation: spin 4s linear infinite;
}
@keyframes spin {
to { --angle: 360deg; }
}
如果去掉 `@property`,这段动画会变成"每 4 秒瞬间闪一次",因为浏览器只能在关键帧之间做离散切换。而一旦注册,`--angle` 就是一个真正的 `<angle>`,可以像 `rotate()` 一样被连续插值,于是整个渐变平滑地旋转起来——全程没有一行 JavaScript。
同理,你也可以写 `transition: --color 0.4s ease`,让一个颜色变量平滑过渡,从而带动整条渐变链、阴影、边框一起变色。这在以前必须靠叠两层元素调 `opacity` 才能勉强模拟。
几个真实的坑
第一,`initial-value` 不是可选项。 很多人复制示例时只写了 `syntax` 和 `inherits`,结果动画毫无效果,排查半天才发现是规则被整条丢弃了。
第二,别把性能想得太美。 注册后确实可以插值了,但动画最终消费的是什么属性,才决定性能。如果 `--angle` 被用在 `conic-gradient` 里,浏览器每帧都要重绘这个背景,这和直接动画 `background` 的开销差不多。少数能被合成线程接管的场景才真正"免费",所以依然要克制使用范围。
第三,`inherits` 会影响重算范围。 设成 `true` 时,动画过程中所有子元素都要跟着重算样式;设成 `false` 能把影响圈在一个元素内。做动画用的属性,建议默认 `false`。
第四,兼容性要留退路。 Chrome/Edge 85+、Safari 16.4+、Firefox 128+ 已经支持,但对老版本可以这样兜底:
@supports (background: paint(x)) {
/* 或者更直接:用 @property 包裹整段逻辑,配合默认样式 */
}
不支持的浏览器会忽略 `@property` 规则,动画不生效,但基础样式仍然正常显示——前提是你给它写了合理的静态默认值。
第五,JS 里也能注册。 `CSS.registerProperty({ name: '--angle', syntax: '<angle>', inherits: false, initialValue: '0deg' })` 是同一套机制的脚本版本,适合需要动态生成的场景。不过能写在 CSS 里的,就别搬进 JS。
它还顺手解决了什么
除了动画,`@property` 还让自定义属性第一次拥有了类型校验。以前写错单位只会在运行时静默失效,现在浏览器会按声明的语法去解析,配合 DevTools 更容易定位问题。
再比如主题切换:把主色注册成 `<color>` 之后,切换深色模式时整个页面的颜色可以有一个连续的过渡,而不是"啪"地一下全变黑。这对体验的提升,比多加几个动效要实在得多。
写在最后
`@property` 看起来只是一个语法糖,但它补上的是 CSS 变量最缺的那块拼图:类型。有了类型,浏览器才知道该怎么插值、怎么校验、怎么优化。对开发者来说,这意味着那些过去必须交给 JS 计算的动态样式——进度条、光晕、跟随鼠标的渐变、主题过渡——现在可以老老实实待在 CSS 里。
下次再遇到"这个变量怎么动不了"的问题,先想想它有没有拿到那张身份证。
转载请注明出处,版权归原作者所有。
管理员





