CSS @property 如何让自定义属性参与类型校验和动画?
解释普通 CSS 自定义属性为什么按 token 保存、不能可靠插值,说明 @property 的 syntax、inherits 和 initial-value,以及注册属性在主题、动画、组件封装和兼容性中的实际边界。
参考答案
简答
- 未注册的自定义属性按一串 token 保存,浏览器不知道它是百分比还是颜色,所以
transition: --x会直接跳到终点。 @property声明syntax、inherits、initial-value,让浏览器按类型计算和插值,属性才能平滑动画。- 不符合
syntax的值在计算时无效,回退到继承值或初始值;但语法校验不等于业务范围校验。 - 注册是全局名字约定,组件私有属性要加前缀;不支持时要退回普通自定义属性,动画只当增强。
三个描述决定属性的语义
CSS
@property --progress {
syntax: '<percentage>';
inherits: false;
initial-value: 0%;
}
.bar {
width: var(--progress);
transition: --progress 300ms ease;
}
.bar[data-progress='done'] {
--progress: 100%;
}
syntax:允许的值类型,如<length>、<color>、<number>,各自有不同的插值规则;inherits:false时子元素不继承,拿到的是initial-value;initial-value:除syntax: '*'外必须提供,且必须是与上下文无关的计算值(例如0px可以,1em不行)。
注册后把 --progress 设成 50px 不符合语法,会回退到初始或继承结果;未注册的属性则会原样保存这个字符串。
inherits 怎样选
组件内部的进度、角度、缩放等局部状态适合 inherits: false,避免父级同名变量污染;主题色、字体、密度等 token 需要往下传,应保持 true。不同库注册同一个名字会共享同一条注册规则,所以私有属性要用稳定前缀。
语法匹配不等于业务校验
<length> 能排除明显不是长度的值,但不会检查“不能为负”“不超过 2rem”这类业务约束,这些仍要在组件 API 或运行时校验。变量展开后不满足目标属性语法时,整条声明在计算时失效,复杂的多层 fallback 要在开发者工具里确认结果。
颜色动画与可访问性
CSS
@property --card-color {
syntax: '<color>';
inherits: false;
initial-value: transparent;
}
.card {
background: var(--card-color);
transition: --card-color 200ms ease;
}
.card[data-active='true'] {
--card-color: rgb(219 234 254);
}
颜色插值空间要按目标浏览器验证。动画只改视觉,用颜色表示错误时仍要配合文字、图标或 aria-invalid。
JavaScript 注册
JavaScript
if ('registerProperty' in CSS) {
CSS.registerProperty({
name: '--progress',
syntax: '<percentage>',
inherits: false,
initialValue: '0%',
});
}
同名属性重复调用 CSS.registerProperty() 会抛出 InvalidModificationError,组件库要保证只执行一次,并考虑热更新和多副本加载。静态的公共属性更适合写在 CSS 里,便于阅读和审查。
兼容与性能
支持范围以 MDN 兼容表为准;不支持时属性仍按普通自定义属性生效,只是没有类型化动画和非继承行为。注册本身很少是瓶颈,关键是它控制什么:每帧改变 <length> 会触发布局,比只改 transform 或 opacity 成本高,应在 Performance 面板实测。