Enter and exit animations using @starting-style and allow-discrete
Exit and enter animations have historically been annoying to implement. Frameworks have made it easier, but what if you just want to use vanilla css? @starting-style is here to solve your problems! @starting-style is available in all modern browsers, some of the of the other css features aren't available in firefox yet, but I'll mention that when we get to it. Let's go!
The goal
We'll be using this popover as an example, but this works for any element that enters and leaves the dom (like an element with display:none;
)
And here's the code if you're in a hurry:
.popover { /* Exit styles */ opacity: 0; transform: translateY(100%); transition: opacity 1s ease, transform 1s ease, display 1s allow-discrete; /* allow-discrete is not supported in firefox as of writing */ } .popover:popover-open { /* Default styles */ transform: translateY(0); opacity: 1; } @starting-style { /* Starting/enter styles */ .popover:popover-open { transform: translateY(-100%); opacity: 0; } }
The explanation
We'll be using this as a starting point:
Starting animations
Previously, there was no way to transition from display: none
to a visible element. A hidden element still has the same styles as when it's visible, so there is nothing to transition from!
@starting-style allows you to define styles for an element just before it's rendered in the dom. This way, the browser has something to use as a starting point.
It looks like this:
@starting-style { your-element { <style-definition> } }
Where your-element
is the selector to select the element you want to animate, and <style-definition>
is where you define your starting styles, simple! Now, if you add a transition, it can transition between the two values:
.popover { transition: opacity 1s ease, transform 1s ease; } .popover:popover-open { transform: translateY(0); opacity: 1; } @starting-style { /* Starting/enter styles */ .popover:popover-open { transform: translateY(-100%); opacity: 0; } }
Magic! It applies the transition when entering! Because of the @starting-style,
the browser knows what to use as the starting point, allowing us to animate the
opacity and transform. (Notice: we're setting @starting-style on the
:popover-open
pseudoclass because we're using a popover. If this was a regular
element you could set it on the basic class/styles)
Exit animations
Enter animations are already a very useful tool to have, but they're only half of the puzzle. Let's look at how to define exit animations as well.
Previously, it wasn't possible to animate properties like display
. The values it accepts aren't like numbers, or colours. You can't map a point halfway between none
and block
; They're separate, discrete values.
Recently, the animation-behavior
property became available in most major browsers (except firefox 😔), and with it came allow-discrete
value. You can use it both as a standalone property, or as part of a transition definition:
.toggle-me { transition: opacity 1s ease, display 1s allow-discrete; } /* Or: */ .toggle-me { transition: opacity 1s ease, display 1s; transition-behavior: allow-discrete; }
When setting this property on a transition for a discrete property, it allows supporting browsers to animate it. Now, instead of instantly flipping the value, it will flip halfway through the transition. It will do this for all discrete values, except display
.
When using it with display
, the browser will instead make sure that the element it's set on will be visible during the whole transition. This allows you to define exit animations!
Let's add it to our popover:
.popover { /* Exit styles */ transition: opacity 1s ease, transform 1s ease, display 1s allow-discrete; /* allow-discrete is not supported in firefox as of writing */ } .popover:popover-open { /*...*/ } @starting-style { /*...*/ }
Notice how it's taking a while to disappear now? Because we added a transition for the display
property, and added allow-discrete
, the browser will wait until the transition is done to actually set the property.
If we then also add an opacity
of 0 to the hidden state, we can also fade it out:
.popover { /* Exit styles */ opacity: 0; transform: translateY(100%); transition: opacity 1s ease, transform 1s ease, display 1s allow-discrete; /* allow-discrete is not supported in firefox as of writing */ } .popover:popover-open { /* Default styles */ transform: translateY(0); opacity: 1; } @starting-style { /* Starting/enter styles */ .popover:popover-open { transform: translateY(-100%); opacity: 0; } }
That's all there is to it!
Browser support
As of writing, @starting-style
is only supported in around 88% of modern browsers. allow-discrete
has slightly worse support, with around 85% support. Those are not great stats. However, the functionalities these properties add are mostly cosmetic (when used that way). This means they're great candidates for progressive enhancement!
You can add them now as a bonus for supporting browsers, and once the other browsers catch up, they'll automatically use them too!
Accessibility
As with all big transitions, don't forget to add @media (prefers-reduced-motion)
around the transitions! I haven't added them in the examples for brevity, but you should!
Conclusion
Using @starting-style
and allow-discrete
together allows you to create smoother experiences for your users. They can be a little tricky to wrap your head around when you first encounter them. But I find it's easiest to build up the animation slowly, bit by bit. Start with the enter animation, then move on to exit.
Hopefully this post helped you out a bit, and you'll make some fun stuff with it. Send me a message if you want to show it off!