配了八年 CSS,每次想给渐变动起来都要靠 JS 插值——今天 @property 把这件事从浏览器底层彻底变了

配了八年 CSS,每次想给渐变动起来都要靠 JS 插值——今天 @property 把这件事从浏览器底层彻底变了

以前写 CSS 动画,渐变色一换就只能靠 JavaScript 逐帧计算再塞回样式表。背景从蓝变紫、进度条从 0 填到 100%,这些在设计稿里流畅的效果,前端实现起来却要写几十行 JS 才能做到。@property 把这个能力还给了 CSS——它让你注册带类型的自定义属性,浏览器自己就会做插值计算,而且整个过程不经过主线程。

这个能力以前是断的。Paint Worklet 能在合成器线程上画任意图形,但它读不到未注册的 CSS 变量,因为那些变量本质上是字符串,浏览器不知道怎么插值。@property 补上了这一环——它把 CSS 自定义属性变成类型安全的、可动画的,Worklet 通过 inputProperties 订阅这些属性变化之后,浏览器会在每次属性更新时自动重绘,整个动画链路在合成器线程上闭环,主线程零负担。

举一个完整的例子。做一个随滚动进度渐变的背景,0% 时是深蓝,100% 时是紫色,中间平滑过渡:

// scroll-gradient.js
registerPaint('scrollGradient', class {
  static get inputProperties() {
    return ['--progress'];
  }
  paint(ctx, geom, properties) {
    const progress = parseFloat(properties.get('--progress')) || 0;
    const r = Math.round(30 + progress * (130 - 30));
    const g = Math.round(20 + progress * (0 - 20));
    const b = Math.round(200 + progress * (180 - 200));
    ctx.fillStyle = `rgb(${r}, ${g}, ${b})`;
    ctx.fillRect(0, 0, geom.width, geom.height);
  }
});
// 注册 worklet
if ('paintWorklet' in CSS) {
  CSS.paintWorklet.addModule('scroll-gradient.js');
}
@property --progress {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

.card {
  --progress: 0;
  background-image: paint(scrollGradient);
  transition: --progress 0.3s;
}

.card:hover {
  --progress: 1;
}

悬停时 --progress 从 0 变到 1,浏览器自动在两者之间做数值插值,Worklet 里的 paint 函数被调用 30~60 次,每次拿到一个连续变化的 progress 值,最终在合成器线程上画出平滑的渐变过渡。没有 JS 定时器,没有 requestAnimationFrame,主线程完全不参与。

这个组合真正的价值在于三件事。第一是主题切换时的平滑过渡——暗色模式切换、主题色从 A 变到 B,靠 JS 插值会有白屏闪烁,靠 @property 加 transition,浏览器在闭包内完成插值,闪烁消失。第二是设计系统 token 的动态化——设计系统里定义的颜色 token 注册为 @property 之后,夜间模式的切换变成了浏览器原生动画,而不是生硬地替换整张色表。第三是 Worklet 永远在合成器线程执行,不卡主线程——即使属性以每秒 60 次的频率变化,页面滚动和交互也不会被打断。

2026 年的浏览器支持情况已经完全变了。@property 在 Chrome 85、Firefox 97、Safari 16.4 全部稳定支持,全球覆盖率超过 91%,已经是 Baseline Widely Available。Paint API 稍晚一些——Chrome 65 和 Edge 79 稳定支持,Firefox 105,Safari 16.2,但 Safari 对 Worklet 的支持长期不稳定,生产环境需要做能力检测。有意思的是 @property 本身就已经值得在生产环境使用,即使你暂时不用 Paint Worklet,它带来的类型安全和插值能力在任何需要动态 CSS 数值的场景都能直接受益。

落地的最小路径:第一步,把设计系统里的颜色和间距 token 全部注册为 @property,这一步工作量不大但收益立竿见影——所有使用这些 token 的动画都会从离散跳变平滑过渡;第二步,找一个高频动画场景(进度条、颜色切换、透明度变化),用 @property 替换现有的 JS 插值代码,代码量通常能从 20~30 行降到 5 行以内;第三步,如果你的产品有大量装饰性图形或需要动态纹理,再考虑引入 Paint Worklet——用它画棋盘格、噪点背景、动态边框,比加载图片或用 Canvas 合成要轻量得多,而且分辨率无关。

需要提醒的是:@property 注册必须写在 CSS 文件根部,且语法字符串必须合法,类型不匹配时浏览器会静默回退到 initial-value;Paint Worklet 有安全限制——不能读取像素、不能访问 DOM、只支持同步绘制逻辑,复杂图片合成场景仍然需要 OffscreenCanvas;Safari 对 Paint Worklet 的支持至今不完整,面向 iOS 用户的产品必须准备 CSS 渐变降级方案。

评论区

0 条评论

登录后可评论。