为什么我不能为自定义属性(又名 CSS 变量)设置动画?

Why can't I animate custom properties (aka CSS variables)?

看这个动画:

为什么金色的div动画不流畅?
是否有任何也使用变量的解决方法?

#one {
  width: 50px;
  height: 50px;
  background-color: gold;
  --o: 0;
  animation: roll-o-1 2s infinite alternate ease-in-out both;
  position: relative;
  left: calc(var(--o) * 1px);
}

@keyframes roll-o-1 {
  0% {
    --o: 0;
  }
  50% {
    --o: 50;
  }
  100% {
    --o: 100;
  }
}

#two {
  width: 50px;
  height: 50px;
  background-color: silver;
  --o: 0;
  animation: roll-o-2 2s infinite alternate ease-in-out both;
  position: relative;
}

@keyframes roll-o-2 {
  0% {
    left: 0px;
  }
  50% {
    left: 50px;
  }
  100% {
    left: 100px;
  }
}
<div id="one"></div>
<br>
<div id="two"></div>

并非所有 CSS 属性都可以设置动画,并且您不能设置 css 变量的动画。这是您可以设置动画的属性列表 https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties

来自 the specification:

Animatable: no

然后

Notably, they can even be transitioned or animated, but since the UA has no way to interpret their contents, they always use the "flips at 50%" behavior that is used for any other pair of values that can’t be intelligently interpolated. However, any custom property used in a @keyframes rule becomes animation-tainted, which affects how it is treated when referred to via the var() function in an animation property.

所以基本上,您可以在 属性 上使用过渡和动画,其中它们的值是使用自定义 属性 定义的,但您不能为自定义 属性.[=23 执行此操作=]

请注意下面示例中的不同之处,我们可能认为两个动画是相同的,但实际上不是。浏览器知道如何动画 left 但不知道如何动画 left 使用的自定义 属性 (也可以在任何地方使用)

#one {
  width: 50px;
  height: 50px;
  background-color: gold;
  animation: roll-o-1 2s infinite alternate ease-in-out both;
  position: relative;
  left: calc(var(--o) * 1px);
}

@keyframes roll-o-1 {
  0% {
    --o: 0;
  }
  50% {
    --o: 50;
  }
  100% {
    --o: 100;
  }
}

#two {
  width: 50px;
  height: 50px;
  background-color: silver;
  --o: 1;
  animation: roll-o-2 2s infinite alternate ease-in-out both;
  position: relative;
}

@keyframes roll-o-2 {
  0% {
    left: calc(var(--o) * 1px);
  }
  50% {
    left: calc(var(--o) * 50px);
  }
  100% {
    left: calc(var(--o) * 100px);
  }
}
<div id="one"></div>
<br>
<div id="two"></div>

另一个使用转换的例子:

.box {
  --c:red;
  background:var(--c);
  height:200px;
  transition:1s;
}
.box:hover {
  --c:blue;
}
<div class="box"></div>

我们有一个过渡,但不是针对自定义 属性。它用于背景,因为在 :hover 状态下,我们正在再次评估该值,因此背景将发生变化并发生过渡。


对于动画,即使在关键帧内定义了left属性,也不会有动画:

#one {
  width: 50px;
  height: 50px;
  background-color: gold;
  animation: roll-o-1 2s infinite alternate ease-in-out both;
  position: relative;
  left: calc(var(--o) * 1px);
}

@keyframes roll-o-1 {
  0% {
    --o: 0;
     left: calc(var(--o) * 1px);
  }
  50% {
    --o: 50;
     left: calc(var(--o) * 1px);
  }
  100% {
    --o: 100;
    left: calc(var(--o) * 1px);
  }
}

#two {
  width: 50px;
  height: 50px;
  background-color: silver;
  --o: 1;
  animation: roll-o-2 2s infinite alternate ease-in-out both;
  position: relative;
}

@keyframes roll-o-2 {
  0% {
    left: calc(var(--o) * 1px);
  }
  50% {
    left: calc(var(--o) * 50px);
  }
  100% {
    left: calc(var(--o) * 100px);
  }
}
<div id="one"></div>
<br>
<div id="two"></div>

当提出这个问题时,无法为自定义属性设置动画,正如@temani afif 正确指出的那样 -

since the UA has no way to interpret their contents

从那时起,CSS Houdini have put together the CSS Properties and Values API specification

This specification extends [css-variables], allowing the registration of properties that have a value type, an initial value, and a defined inheritance behaviour, via two methods:

A JS API, the registerProperty() method

A CSS at-rule, the @property rule

现在您可以注册自己的自定义属性 - 包括自定义类型 属性 - 动画自定义 属性 成为可能。

要通过 CSS 注册自定义 属性 - 使用 @property 规则

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

#one {
  width: 50px;
  height: 50px;
  background-color: gold;
  --o: 0;
  animation: roll-o-1 2s infinite alternate ease-in-out both;
  position: relative;
  left: calc(var(--o) * 1px);
}

@keyframes roll-o-1 {
  0% {
    --o: 0;
  }
  50% {
    --o: 50;
  }
  100% {
    --o: 100;
  }
}

#two {
  width: 50px;
  height: 50px;
  background-color: silver;
  animation: roll-o-2 2s infinite alternate ease-in-out both;
  position: relative;
}

@keyframes roll-o-2 {
  0% {
    left: 0px;
  }
  50% {
    left: 50px;
  }
  100% {
    left: 100px;
  }
}

@property --o {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}
<div id="one"></div>
<br>
<div id="two"></div>

要通过 javascript 注册 属性 - 使用 CSS.registerProperty() 方法:

CSS.registerProperty({
      name: "--o",
      syntax: "<number>",
      initialValue: 0,
      inherits: "false"
   });

CSS.registerProperty({
  name: "--o",
  syntax: "<number>",
  initialValue: 0,
  inherits: "false"
});
#one {
  width: 50px;
  height: 50px;
  background-color: gold;
  --o: 0;
  animation: roll-o-1 2s infinite alternate ease-in-out both;
  position: relative;
  left: calc(var(--o) * 1px);
}

@keyframes roll-o-1 {
  0% {
    --o: 0;
  }
  50% {
    --o: 50;
  }
  100% {
    --o: 100;
  }
}

#two {
  width: 50px;
  height: 50px;
  background-color: silver;
  animation: roll-o-2 2s infinite alternate ease-in-out both;
  position: relative;
}

@keyframes roll-o-2 {
  0% {
    left: 0px;
  }
  50% {
    left: 50px;
  }
  100% {
    left: 100px;
  }
}
<div id="one"></div>
<br>
<div id="two"></div>

注意

Browser support 目前仅限于 chrome([=19= 为 v78+,@property 为 v85+)edge 和 opera

我可以用新的 CSS Properties and Values API Level 1
(CSS Houdini 的一部分;W3C 工作草案,截至 2020 年 10 月 13 日)

我只需要使用 @property 规则

注册我的自定义 属性
    @property --o {
      syntax: "<number>";
      inherits: true;
      initial-value: 0;
    }

通过 syntax 属性 我声明此自定义 属性 为 类型 <number>,这提示浏览器属性 的过渡或动画计算应该以何种方式进行。

列出 syntax 属性 支持的值 here

Browser compatibility is surprisingly strong, since this is an experimental feature and in draft status (See caniuse 也)。 Chrome Edge 支持,Firefox 和 Safari 不支持。

@property --o {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

#one {
  width: 50px;
  height: 50px;
  background-color: gold;
  --o: 0;
  animation: roll-o-1 2s infinite alternate ease-in-out both;
  position: relative;
  left: calc(var(--o) * 1px);
}

@keyframes roll-o-1 {
  0% {
    --o: 0;
  }
  50% {
    --o: 50;
  }
  100% {
    --o: 100;
  }
}

#two {
  width: 50px;
  height: 50px;
  background-color: silver;
  --o: 0;
  animation: roll-o-2 2s infinite alternate ease-in-out both;
  position: relative;
}

@keyframes roll-o-2 {
  0% {
    left: 0px;
  }
  50% {
    left: 50px;
  }
  100% {
    left: 100px;
  }
}
<div id="one"></div>
<br>
<div id="two"></div>

也许不是您要找的答案,但我使用 javascript 动画(fx with gsap)实现了这个

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        body,html {
            height: 100%;
            display: flex;
        }

        .wrapper {
            margin: auto 0;
        }

        .box {
            --animate:0;
            background-color: tomato;
            height: 70px;
            width: 70px;
            transform: translateX(calc(var(--animate) * 1px)) rotate(calc(var(--animate) * 1deg));
        }
    </style>
</head>
<body>
    
    <div class="wrapper">
        <div class="box"></div>
        <button onclick="play()">Play</button>
    </div>
    
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js" integrity="sha512-H6cPm97FAsgIKmlBA4s774vqoN24V5gSQL4yBTDOY2su2DeXZVhQPxFK4P6GPdnZqM9fg1G3cMv5wD7e6cFLZQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
    <script>
        const tween = gsap.to(".box",{
            "--animate":900,   
            duration:10
        })

        tween.pause();

        function play() {
            tween.progress(0);
            tween.play();
        }
    </script>
</body>
</html>