Exploring @property and its Animating Powers

414 阅读6分钟

Exploring @property and its Animating Powers

Jhey Tompkins on Mar 4, 2021

Uh, what’s @property? It’s a new CSS feature! It gives you superpowers. No joke, there is stuff that @property can do that unlocks things in CSS we’ve never been able to do before.

While everything about @property is exciting, perhaps the most interesting thing is that it provides a way to specify a type for custom CSS properties. A type provides more contextual information to the browser, and that results in something cool: We can give the browser the information is needs to transition and animate those properties!

But before we get too giddy about this, it’s worth noting that support isn’t quite there. As it current stands at the time of this writing, @property is supported in Chrome and, by extension, Edge. We need to keep an eye on browser support for when we get to use this in other places, like Firefox and Safari.

First off, we get type checking

That’s right! Type checking in CSS. It’s sorta like creating our very own mini CSS specification. And that’s a simple example. Check out all of the various types we have at our disposal:

image.png

  • length
  • number
  • percentage
  • length-percentage
  • color
  • image
  • url
  • integer
  • angle
  • time
  • resolution
  • transform-list
  • transform-function
  • custom-ident (a custom identifier string)

Before any of this, we may have relied on using “tricks” for powering animations with custom properties.

What cool stuff can we do then? Let’s take a look to spark our imaginations.

Let’s animate color

How might you animate an element either through a series of colors or between them? I’m a big advocate for the HSL color space which breaks things down into fairly understandable numbers: hue, saturation, and lightness, respectively.

Animating a hue feels like something fun we can do. What’s colorful? A rainbow! There’s a variety of ways we could make a rainbow. Here’s one:

image.png

In this example, CSS Custom Properties are set on the different bands of the rainbow using :nth-child() to scope them to individual bands. Each band also has an --index set to help with sizing.

To animate those bands, we might use that --index to set some negative animation delays, but then use the same keyframe animation to cycle through hues.

image.png That might work out okay if you want a “stepped” effect. But, those keyframe steps aren’t particularly accurate. I’ve used steps of 14% as a rough jump.

Step2.gif

We could animate the border-color and that would get the job done. But, we’d still have a keyframe step calculation issue. And we need to write a lot of CSS to get this done:

image.png

Enter @property. Let’s start by defining a custom property for hue. This tells the browser our custom property, --hue, is going to be a number (not a string that looks like a number):

image.png Hue values in HSL can go from 0 to 360. We start with an initial value of 0. The value isn’t going to inherit. And our value, in this case, is a number. The animation is as straightforward as:

image.png Yep, that’s the ticket:

Step3.gif

To get the starting points accurate, we could play with delays for each band. This gives us some cool flexibility. For example, we can up the animation-duration and we get a slow cycle. Have a play with the speed in this demo.

image.png

It may not be the “wildest” of examples, but I think animating color has some fun opportunities when we use color spaces that make logical use of numbers. Animating through the color wheel before required some trickiness. For example, generating keyframes with a preprocessor, like Stylus:

image.png We do this purely because this isn’t understood by the browser. It sees going from 0 to 360 on the color wheel as an instant transition because both hsl values show the same color.

image.png The keyframes are the same, so the browser assumes the animation stays at the same background value when what we actually want is for the browser to go through the entire hue spectrum, starting at one value and ending at that same value after it goes through the motions.

Think of all the other opportunities we have here. We can:

  • animate the saturation
  • use different easings
  • animate the lightness
  • Try rgb()
  • Try degrees in hsl() and declare our custom property type as

What’s neat is that we can share that animated value across elements with scoping! Consider this button. The border and shadow animate through the color wheel on hover.

Step5.gif

Animating color leads me think… wow!

Step6-min-2.gif

Straight-up numbering

Because we can define types for numbers—like integer and number—that means we can also animate numbers instead of using those numbers as part of something else. Carter Li actually wrote an article on this right here on CSS-Tricks. The trick is to use an integer in combination with CSS counters. This is similar to how we can work the counter in “Pure CSS” games like this one.

The use of counter and pseudo-elements provides a way to convert a number to a string. Then we can use that string for the content of a pseudo-element. Here are the important bits:

image.png

Which gives us something like this. Pretty cool.

Step7.gif

Take that a little further and you’ve got yourself a working stopwatch made with nothing but CSS and HTML. Click the buttons! The rad thing here is that this actually works as a timer. It won’t suffer from drift. In some ways it may be more accurate than the JavaScript solutions we often reach for such as setInterval. Check out this great video from Google Chrome Developer about JavaScript counters.

Step8.gif

What other things could you use animated numbers for? A countdown perhaps?

Animated gradients

You know the ones, linear, radial, and conic. Ever been in a spot where you wanted to transition or animate the color stops? Well, @property can do that!

Consider a gradient where we‘re creating some waves on a beach. Once we’ve layered up some images we could make something like this.

image.png

There is quite a bit going on there. But, to break it down, we’re creating each color stop with calc(). And in that calculation, we add the value of --wave. The neat trick here is that when we animate that --wave value, all the wave layers move.

Step9.gif

This is all the code we needed to make that happen:

image.png Without the use of @property, our waves would step between high and low tide. But, with it, we get a nice chilled effect like this.

It’s exciting to think other neat opportunities that we get when manipulating images. Like rotation. Or how about animating the angle of a conic-gradient… but, within a border-image. Bramus Van Damme does a brilliant job covering this concept.

Let’s break it down by creating a charging indicator. We’re going to animate an angle and a hue at the same time. We can start with two custom properties:

image.png

The animation will update the angle and hue with a slight pause on each iteration.

image.png

Now let’s apply it as the border-image of an element.

Pretty cool.

Step11.gif Unfortunately, border-image doesn‘t play nice with border-radius. But, we could use a pseudo-element behind it. Combine it with the number animation tricks from before and we’ve got a full charging/loading animation. (Yep, it changes when it gets to 100%.)

Step12.gif

Transforms are cool, too

One issue with animating transforms is transitioning between certain parts. It often ends up breaking or not looking how it should. Consider the classic example of a ball being throw. We want it to go from point A to point B while imitating the effect of gravity.

An initial attempt might look like this

image.png

But, we’ll soon see that it doesn’t look anything like we want.

Step13.gif

Before, we may have reached for wrapper elements and animated them in isolation. But, with @property, we can animate the individual values of the transform. And all on one timeline. Let’s flip the way this works by defining custom properties and then setting a transform on the ball.

image.png

Now for our animation, we can compose the transform we want against the keyframes:

image.png

The result? The curved path we had hoped for. And we can make that look different depending on the different timing functions we use. We could split the animation into three ways and use different timing functions. That would give us different results for the way the ball moves.

Step14.gif

Consider another example where we have a car that we want to drive around a square with rounded corners.

Step15.gif

We can use a similar approach to what we did with the ball:

image.png

The car’s transform is using calculated with vmin to keep things responsive:

Now can write an extremely accurate frame-by-frame journey for the car. We could start with the value of --x.

image.png

The car makes the right journey on the x-axis.

Step16.gif Then we build upon that by adding the travel for the y-axis:

image.png

Well, that’s not quite right.

Step17.gif

Let’s drop some extra steps into our @keyframes to smooth things out:

image.png

Ah, much better now:

Step18.gif

All that‘s left is the car‘s rotation. We‘re going with a 5% window around the corners. It’s not precise but it definitely shows the potential of what’s possible:

image.png

image.png

And there we have it, a car driving around a curved square! No wrappers, no need for complex Math. And we composed it all with custom properties.

Step19.gif

Powering an entire scene with variables

We‘ve seen some pretty neat @property possibilities so far, but putting everything we’ve looked at here together can take things to another level. For example, we can power entire scenes with just a few custom properties.

Consider the following concept for a 404 page. Two registered properties power the different moving parts. We have a moving gradient that’s clipped with -webkit-background-clip. The shadow moves by reading the values of the properties. And we swing another element for the light effect.

Step20.gif

That’s it!

It’s exciting to think about what types of things we can do with the ability to define types with @property. By giving the browser additional context about a custom property, we can go nuts in ways we couldn’t before with basic strings.

What ideas do you have for the other types? Time and resolution would make for interesting transitions, though I’ll admit I wasn’t able to make them work that way I was hoping. url could also be neat, like perhaps transitioning between a range of sources the way an image carousel typically does. Just brainstorming here!

I hope this quick look at @property inspires you to go check it out and make your own awesome demos! I look forward to seeing what you make. In fact, please share them with me here in the comments!