CSS Animation Superpowers with @property
Tags: css, webdev, animation, tutorial
Published: 2021-02-01 19:19:04

There's a new CSS feature on the way that will give you CSS animation superpowers!

For those in camp **TL;DR** , `@property` provides a way for the browser to type check your CSS variables. By doing that, the browser is then able to animate and transition those properties!

```css
@property --spinAngle {
  initial-value: 0deg;
  inherits: false;
  syntax: '<angle>';
}

@keyframes spin {
  to {
    --spinAngle: 360deg;
  }
}
```

[Browser compatibility](https://caniuse.com/mdn-css_at-rules_property) is getting there too.

Have a play with the demos in this [CodePen collection](https://codepen.io/collection/AazyEP).

{% codepen https://codepen.io/jh3y/pen/OJRLMxE %}

You can also watch this screencast where I go through how to use it.

{% youtube qrU88BoVFB0 %}

For those sticking around, let's dig in and see what we can do with CSS variables and animation.

* * *

Something I've always enjoyed doing is trying to push the limits on my CSS animation. When CSS variables came about, one of my first questions was "Can we animate them?". The answer was "No".

But, we could still use them in our CSS animations for different reasons.

We couldn't animate the properties. But, we could use them to make our animation dynamic. In fact, I tweeted about it when I found out.

{% twitter 1191695646752948225 %}

This demo even got printed in a magazine!

![Demo shown in net magazine - January 2020](https://res.cloudinary.com/jh3yy/image/upload/v1611767145/jhey.dev/Posts/netmagazine-mention_pnwc9b.jpg)

This makes for some very interesting opportunities when you start inlining properties. Check out [this article](https://jhey.dev/writing/the-power-and-fun-of-scope-with-css-custom-properties/) about the power and fun of variable scope.

One of the neatest things you can do with CSS variables and animation is using them for composition. By that, we mean using them to define the speeds and delays of animations. That makes it easier to manage things like timelines. In fact, Carl Schoof did a fun animation challenge series on this. Here was my 3b solution.

{% codepen https://codepen.io/jh3y/pen/dyYbxda %}

Here's a roundup of how this is working (Skip to the 6-minute mark).

{% youtube J8DpbqSR-r8 %}

## The New @property

What does the new `@property` give us then? Let's start by saying it's still making its way into browsers. Currently, Chrome and Edge have good support. You can check out [browser compatibility here](https://caniuse.com/mdn-css_at-rules_property).

This new feature allows us to define types for your custom properties. Types like `angle`, `number`, `percentage`, etc.

This "type checking" gives the browser extra contextual information. It can use this to transition and animate custom property values. That's the magic right there.

This means we can animate things that once we could not.

- Color stops in a `linear-gradient`
- Angle of a `conic-gradient`
- Hue of a HSL color
- Individual transform values
- Whatever else your imagination can think of!

{% codepen https://codepen.io/jh3y/pen/YzpKKoN %}

How do we register a custom property? It's as straight forward as

```css
@property --spinAngle {
  initial-value: 0deg;
  inherits: false;
  syntax: '<angle>';
}
```

- `inherits` - Whether the property inherits the value from its parent
- `initial-value` - Fallback/initial value
- `syntax` - Property type. Check out [other types here](https://web.dev/at-property/#syntax).

## Examples

Let's walk through some examples.

### Waves

Let's start with some waves. For this example, we don't even need any elements. We're going to animate a `linear-gradient` on the `body`.

Let's start by defining our custom property `--wave`.

```css
@property --wave {
  inherits: false;
  initial-value: 0%;
  syntax: '<percentage>';
}
```

Then we'll create a `linear-gradient` that makes waves using `calc` and our `wave` property.

```css
background: linear-gradient(transparent 0 calc(35% + (var(--wave) * 0.5)), var(--wave-four) calc(75% + var(--wave)) 100%),
            linear-gradient(transparent 0 calc(35% + (var(--wave) * 0.5)), var(--wave-three) calc(50% + var(--wave)) calc(75% + var(--wave))),
            linear-gradient(transparent 0 calc(20% + (var(--wave) * 0.5)), var(--wave-two) calc(35% + var(--wave)) calc(50% + var(--wave))),
            linear-gradient(transparent 0 calc(15% + (var(--wave) * 0.5)), var(--wave-one) calc(25% + var(--wave)) calc(35% + var(--wave))), var(--sand);
```

> That might look a little confusing. We're layering up background images to create many waves. We then use <code>calc</code> to calculate the color stops for each individual gradient.

Here are the static waves.

{% codepen https://codepen.io/jh3y/pen/GRNKqWy %}

To bring them to life. Animate that value.

```css
body {
 animation: waves 5s infinite ease-in-out;
}
@keyframes waves {
  50% {
    --wave: 25%;
  }
}
```

And we have animated waves

{% codepen https://codepen.io/jh3y/pen/YzpKKoN %}

### Party Button

Playing with HSL is a lot of fun. But creating a good animation that loops through the hue wheel is a little tricky. It's doable with preprocessors. I've done things like this in the past

```css
@keyframes party
  for $frame in (0..100)
    {$frame * 1%}
      background 'hsl(%s, 65%, 40%)' % ($frame * 3.6)
```

But, with @property, we can animate the hue itself! Hover the button in this pen.

{% codepen https://codepen.io/jh3y/pen/OJRLMxE %}

The border color will animate through the hue spectrum. And it's as easy as this

```css
@property --hue {
  syntax: '<integer>';
  inherits: true;
  initial-value: 0;
}
button {
  --border: hsl(var(--hue, 0), 0%, 50%);
  border-color: var(--border);
}
button:hover {
  --border: hsl(var(--hue, 0), 80%, 50%);
  animation: hueJump 0.75s infinite linear;
}
@keyframes hueJump {
  to {
    --hue: 360;
  }
}

```

### Travelling Car

This last one's interesting. We want to make a little car go around the track. And we're going to use `transform`. This is already possible if we use wrapper elements. But, with `@property`, we won't have to. We can animate separate properties on the same keyframes.

{% codepen https://codepen.io/jh3y/pen/vYyBKeW %}

To position the car, we've created three properties.

```css
@property --x {
  inherits: false;
  initial-value: -22.5;
  syntax: '<number>';
}
@property --y {
  inherits: false;
  initial-value: 0;
  syntax: '<number>';
}
@property --r {
  inherits: false;
  initial-value: 0deg;
  syntax: '<angle>';
}
```

The `--x` and `--y` properties will dictate the x and y position of the car. Whilst, `--r` will dictate the rotation. We're using `number` because we want to keep the scene responsive and I know the road is a `50vmin` square.

The transform for the car looks like this:

```css
transform: translate(calc(var(--x) * 1vmin), calc(var(--y) * 1vmin)) rotate(var(--r));
```

To animate the car, we'll create one set of keyframes called "journey". And let's start by animating the `--x` property.

```css
@keyframes journey {
  0%, 100% {
    --x: -22.5;
  }
  25% {
    --x: 0;
  }
  50% {
    --x: 22.5;
  }
  75% {
    --x: 0;
  }
}
```

That's going to give us the correct x-coordinate animation.

{% codepen https://codepen.io/jh3y/pen/zYoOBWq %}

Let's add the `--y` property updates to the keyframes.

```css
@keyframes journey {
  0%, 100% {
    --x: -22.5;
    --y: 0;
  }
  25% {
    --x: 0;
    --y: -22.5;
  }
  50% {
    --x: 22.5;
    --y: 0;
  }
  75% {
    --x: 0;
    --y: 22.5;
  }
}
```

And now the car moves in a diamond shape. Not quite right.

{% codepen https://codepen.io/jh3y/pen/mdObELx %}

To combat that, we need to make sure we have the correct `--x` and `--y` at the corners. We could do that with some extra steps in the keyframes.

```css
@keyframes journey {
  0%, 100% {
    --x: -22.5;
    --y: 0;
  }
  12.5% {
    --x: -22.5;
    --y: -22.5;
  }
  25% {
    --x: 0;
    --y: -22.5;
  }
  37.5% {
    --y: -22.5;
    --x: 22.5;
  }
  50% {
    --x: 22.5;
    --y: 0;
  }
  62.5% {
    --x: 22.5;
    --y: 22.5;
  }
  75% {
    --x: 0;
    --y: 22.5;
  }
  87.5% {
    --x: -22.5;
    --y: 22.5;
  }
}
```

And that gives us the car in the right position!

{% codepen https://codepen.io/jh3y/pen/BaQBzVG %}

Last up, the rotation. For this, we can update the `--r` property where we want by adding in extra steps for our keyframes. Here, we've gone with a 5% window around the corners.

```css
@keyframes journey {
  0% {
    --x: -22.5;
    --y: 0;
    --r: 0deg;
  }
  10% {
    --r: 0deg;
  }
  12.5% {
    --x: -22.5;
    --y: -22.5;
  }
  15% {
    --r: 90deg;
  }
  25% {
    --x: 0;
    --y: -22.5;
  }
  35% {
    --r: 90deg;
  }
  37.5% {
    --y: -22.5;
    --x: 22.5;
  }
  40% {
    --r: 180deg;
  }
  50% {
    --x: 22.5;
    --y: 0;
  }
  60% {
    --r: 180deg;
  }
  62.5% {
    --x: 22.5;
    --y: 22.5;
  }
  65% {
    --r: 270deg;
  }
  75% {
    --x: 0;
    --y: 22.5;
  }
  85% {
    --r: 270deg;
  }
  87.5% {
    --x: -22.5;
    --y: 22.5;
  }
  90% {
    --r: 360deg;
  }
  100% {
    --x: -22.5;
    --y: 0;
    --r: 360deg;
  }
}
```

And that's it! The little car image will make it's journey around the square. No wrapper elements required. We don't even need to write out extra transforms.

{% codepen https://codepen.io/jh3y/pen/jOVNqjv %}

## That's it!

Check out the demos in this [collection](https://codepen.io/collection/AazyEP). There are exciting times ahead with CSS Houdini. I'm excited about it. I'm also excited to see what you'll make!

{% codepen https://codepen.io/jh3y/pen/OJRLMxE %}