yunzen
yunzen

Reputation: 33439

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

See this animation:

Why doesn't the golden div animate smoothly?
Is there any workaround which also uses variables?

#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>

Upvotes: 17

Views: 13733

Answers (5)

yunzen
yunzen

Reputation: 33439

I can do this with the new CSS Properties and Values API Level 1
(part of CSS Houdini; W3C Working Draft, as of 13 October 2020)

I only need to register my custom property with the @property rule

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

Via the syntax property I declare this custom property to be of type <number>, which hints the Browser in which way the calculations for transitioning or animating of this property should take place.

Supported values for the syntax property are listed here

"<length>"
"<percentage>"
"<length-percentage>"
"<color>"
"<image>"
"<url>"
"<integer>"
"<angle>"
"<time>"
"<resolution>"
"<transform-function>"
"<custom-ident>"

Browser compatibility is surprisingly strong, since this is an experimental feature and in draft status (See caniuse also). Chrome and Edge support it, Firefox and Safari don't.

<edit> Safari supports it since 16.4. Firefox will support it in the release 118, which is the current nightly build and will be released in late september 2023: </edit>

<edit> The release with Firefox 118 seems to be postponed. As it is scheduled now, it will be released with version 120, which is the current Nightly, released on 21 Nov 2023. Alas, I cannot confirm this working in the Nightly.

@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>

Upvotes: 6

Boris Grunwald
Boris Grunwald

Reputation: 2712

Maybe not the answer you're looking for, but I achieved this using javascript animation (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>

Upvotes: 0

Temani Afif
Temani Afif

Reputation: 273090

From the specification:

Animatable: no

Then

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.

So basically, you can have transition and animation on property where their value are defined with a custom property but you cannot do it for the custom property.

Notice the difference in the below examples where we may think that both animation are the same but no. The browser know how to animate left but not how to animate the custom property used by left (that can also be used anywhere)

#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>

Another example using transition:

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

We have a transition but not for the custom property. It's for the backgroud because in the :hover state we are evaluating the value again thus the background will change and the transition will happen.


For the animation, even if you define the left property within the keyframes, you won't have an animation:

#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>

Upvotes: 11

Danield
Danield

Reputation: 125521

When this question was asked, it wasn't possible to animate custom properties, as @temani afif correctly pointed out -

since the UA has no way to interpret their contents

Since then, 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

So now that you can register your own custom properties - including the type of the custom property - animating the custom property becomes possible.

To register the custom property via CSS - use the @property rule

@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>

To register the property via javascript - use the CSS.registerProperty() method:

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>

NB

Browser support is currently limited to chrome (v78+ for registerProperty(), v85+ for @property) edge and opera

Upvotes: 21

arieljuod
arieljuod

Reputation: 15838

Not all CSS properties are animatable, and you cannot animate css variables. This is the list of the properties you can animate https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties

Upvotes: 4

Related Questions