配了三年渐变动画,每次从蓝变紫都要靠 JS 插值——今天 CSS 自己会了,@property 把这件事彻底原生化了

配了三年渐变动画,每次从蓝变紫都要靠 JS 插值或者直接跳变——今天 CSS 自己会了,@property 把这件事彻底原生化了。


背景:为什么 CSS 变量不能做动画?

传统 CSS 变量的本质是无类型的字符串:

.card {
  --gradient: linear-gradient(135deg, #667eea, #764ba2);
  transition: --gradient 0.5s;
}

浏览器拿到 --gradient 只知道它是个字符串,不知道它是渐变色、角度还是数字——所以 transition 直接忽略它。这是 CSS 变量从第一天就有的局限,很多团队因此上了 GSAP 或者写 JS 定时器。

@property 三件套:让变量「有型」

CSS Houdini 里的 @property 允许你注册一个带类型约束和初始值的变量,浏览器从此「认识」这个变量的语义:

@property --angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

@property --grad-color-1 {
  syntax: "<color>";
  inherits: false;
  initial-value: #667eea;
}

@property --grad-color-2 {
  syntax: "<color>";
  inherits: false;
  initial-value: #764ba2;
}

三个字段:

  • syntax:类型声明,支持 <color><angle><length><integer><gradient> 等 20+ 类型
  • inheritsfalse = 不向子元素扩散,组件自有;true = 向下继承
  • initial-value:初始值,必须符合 syntax 声明的类型

注册完之后,浏览器终于能理解这些变量,从而让 transition 和 animation 起效。

场景一:渐变色 hover 平滑过渡

hover 时渐变色丝滑切换:

.card {
  --grad-color-1: #667eea;
  --grad-color-2: #764ba2;
  background: linear-gradient(135deg, var(--grad-color-1), var(--grad-color-2));
  transition: --grad-color-1 0.4s, --grad-color-2 0.4s;
}
.card:hover {
  --grad-color-1: #f093fb;
  --grad-color-2: #f5576c;
}

hover 时两个颜色值平滑插值过渡,完全原生 CSS,没有任何 JS。

场景二:旋转渐变边框(持续动画)

@property 驱动 @keyframes,让动画一直跑:

@property --angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.gradient-border {
  --angle: 0deg;
  border: 4px solid;
  border-image: conic-gradient(
    from var(--angle),
    #ff6b6b, #4ecdc4, #45b7d1, #ff6b6b
  ) 1;
  animation: spin-angle 3s linear infinite;
}

@keyframes spin-angle {
  to { --angle: 360deg; }
}

conic-gradient 的起始角度是变量驱动,animation 让它持续旋转——这在以前只有伪元素 + canvas 或者 GSAP 才能做,现在纯 CSS 几行搞定。

场景三:骨架屏脉冲(number 类型)

<number> 类型也支持,这在骨架屏 loading 动画里很实用:

@property --pulse-lightness {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 90%;
}

.skeleton {
  background: linear-gradient(
    90deg,
    hsl(210, 10%, 90%),
    hsl(210, 10%, var(--pulse-lightness)),
    hsl(210, 10%, 90%)
  );
  background-size: 200% 100%;
  animation: pulse-bg 1.5s ease-in-out infinite;
}

@keyframes pulse-bg {
  0%, 100% { --pulse-lightness: 90%; }
  50% { --pulse-lightness: 96%; }
}

比传统的 background-position 滑动方案更直观,动画控制更精确,而且一个变量管一个效果,改起来容易。

场景四:组件级样式隔离(inherits: false)

@propertyinherits: false 还有一个和动画无关但同样重要的用法——组件样式隔离:

@property --card-bg {
  syntax: "<color>";
  inherits: false;
  initial-value: white;
}

.card {
  --card-bg: #f0f0f0;
  background: var(--card-bg);
}
.card.warning {
  --card-bg: #fff3cd;
}
.card .child {
  background: var(--card-bg);
}

父元素覆盖 --card-bg,子元素如果也声明了同名变量(且 inherits: false),会保持自己的初始值。这和 CSS 变量默认继承行为正好相反,是做设计系统 token 隔离的好工具。

兼容性:可以上生产了

  • Chrome/Edge 85+
  • Firefox 128+
  • Safari 17.2+

主流浏览器全部就位,生产环境完全可以直接用。搭配 @supports 做渐进增强:

@supports (background: linear-gradient(red, blue)) {
  .card {
    /* 浏览器支持渐变过渡增强 */
  }
}

下一步

下次遇到「渐变色不能动画」「hover 颜色跳变」,先检查有没有注册 @property——这比找 GSAP 文档快多了。如果你有多个组件都要独立动画,把 inherits: false 配上,设计系统的 token 隔离也顺带解决了。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 473 阅读