keyframes – CSS-Tricks https://css-tricks.com Tips, Tricks, and Techniques on using Cascading Style Sheets. Tue, 30 Nov 2021 17:26:38 +0000 en-US hourly 1 https://wordpress.org/?v=6.1.1 https://i0.wp.com/css-tricks.com/wp-content/uploads/2021/07/star.png?fit=32%2C32&ssl=1 keyframes – CSS-Tricks https://css-tricks.com 32 32 45537868 Diagonal Stripes Wipe Animation https://css-tricks.com/diagonal-stripes-wipe-animation/ https://css-tricks.com/diagonal-stripes-wipe-animation/#respond Tue, 30 Nov 2021 17:26:35 +0000 https://css-tricks.com/?p=357109 I was playing this game on Apple Arcade the other day called wurdweb. It’s a fun little game! Little touches like the little shape dudes that walk around the screen (but otherwise don’t do anything) give it a lot …


Diagonal Stripes Wipe Animation originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I was playing this game on Apple Arcade the other day called wurdweb. It’s a fun little game! Little touches like the little shape dudes that walk around the screen (but otherwise don’t do anything) give it a lot of character. I kinda want little shape dudes that walk around on websites. But another UI choice caught my eye, the way that transitions between screens have these diagonal lines that grow and fill the screen, like window blinds closing, kinda.

Here’s a quick screencast showing how those wipes work:

I wanted to have a crack at building this.

The first thing that went through my mind is repeating-linear-gradient and how that can be used to build stripes. So say we set up like this:

.gradient {
  background-image:
    repeating-linear-gradient(
      45deg,
      #ff8a00,
      #ff8a00 10px,
      #e52e71 10px,
      #e52e71 20px
    );
}

That would buy us stripes like this:

We can use transparent as a color though. Meaning if we covered the screen with stripes like these, we could see through where that color is. Say like this:

In that gradient definition, we use 10px as the “start” and 20px as the “end” of the gradient before it repeats. Part of the trick here is keeping that 20px “end” the same and animating the “start” number up to it. When we do that, it actually covers the screen in a solid color. The problem is… how do you animate it? You can’t do this:

Screenshot of a CSS code snippet on a dark gray background with syntax highlighting. An arrow is pointing from the repeating linear gradient on the element to another repeating linear gradient inside keyframes. A note that says not going to animate is displayed in large white letters above a crying emoji.

What we need to do is animate that “start” pixel value number alone. We can use a custom property, but it’s a little tricky because without declaring them, custom properties are just strings, and not animatable lengths. So we’d have to do it like this.

@property --start {
  syntax: "<length>";
  inherits: false;
  initial-value: 10px;
}
#cover {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-image: repeating-linear-gradient(
    45deg,
    #ff8a00,
    #ff8a00 var(--start),
    transparent var(--start),
    transparent var(--end, 20px)
  );
  animation: cover 1s linear infinite;
}
@keyframes cover {
  to {
    --start: 20px;
  }
}

We’ve got to use @property here to do this, which I really like but, sadly, has limited browser support. It does work though! I’ve got all that set up, including a quick prefers-reduced-motion media query. I’m using a smidge of JavaScript to change the background halfway through the animation (while the screen is covered) so you can see how it might be used for a screen transition. Again, note that this is only working in Chromium-based browsers at the moment:

Notice I’ve used CSS custom properties for other things as well, like the angle and size of the stripes and the speed of the animation. They are both very trivial to change! I’ve chucked in knobs so you can adjust things to your liking. Knobs? Yeah, they are cool:

Like and subscribe

This whole thing started as a tweet. In this case, I’m glad I did as Temani Afif chimed in with a way to do it with masks as well, meaning pretty solid support across all browsers:

I don’t think animating background color stops or a mask position is particularly performant, but since we’re talking “screen wipes” here, one could imagine that the page isn’t likely to be interacted with anymore until the page transition is over, so maybe that’s not the world’s biggest deal.


Diagonal Stripes Wipe Animation originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/diagonal-stripes-wipe-animation/feed/ 0 357109
Recreating the Apple Music Hits Playlist Animation in CSS https://css-tricks.com/recreating-the-apple-music-hits-playlist-animation-in-css/ https://css-tricks.com/recreating-the-apple-music-hits-playlist-animation-in-css/#comments Mon, 29 Nov 2021 16:38:49 +0000 https://css-tricks.com/?p=357565 Apple Music has this “Spatial Audio” feature where the direction of the music in your headphones is based on the location of the device. It’s tough to explain just how neat it is. But that’s not what I’m here to …


Recreating the Apple Music Hits Playlist Animation in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Apple Music has this “Spatial Audio” feature where the direction of the music in your headphones is based on the location of the device. It’s tough to explain just how neat it is. But that’s not what I’m here to talk about.

I opened up the Apple Music app and saw a featured playlist of hit songs that support Spatial Audio. The cover for it is this brightly-colored pink container that holds a bunch of boxes stacked one on top of another. The boxes animate in one at a time, fading in at the center of the container, then fading out as it scales to the size of the container. Like an infinite loop.

Animated GIF showing the Apple Music UI we are recreating. It's brightly colored shades of pink against a dark gray background with information about the playlist to the right of the pattern, and options to play and shuffle the sings in orange buttons.

Cool! I knew I had to re-create it in CSS. So I did.

Here’s how it works…

The markup

I started with the HTML. There’s obviously a container we need to define, plus however many boxes we want to animate. I went with an even 10 boxes in the container.

<div class="container">
  <div class="box"></div>
  <div class="box"></div>
  <div class="box"></div>
  <!-- etc. -->
</div>

That’s literally it for HTML. We are free to jump right into the CSS!

Styling the container

Nothing too fancy here. I measured approximate dimensions based on what I saw in Apple Music, which happened to be 315px × 385px. then I took a screenshot of the cover and dropped it into my image editing app to get the lightest possible color, which is around the outside edges of the container. My color picker landed on #eb5bec.

.container {
  background-color: #eb5bec;
  height: 315px;
  width: 385px;
}

As I was doing this, I knew I would probably want this to be a grid container to align the boxes and any other elements in the center. I also figured that the boxes themselves would start from the center of the container and stack on top of one another, meaning there will be some absolute positioning. That also means the container ought to have relative positioning to reign them in.

.container {
  background-color: #eb5bec;
  height: 315px;
  position: relative;
  width: 385px;
}

And since we want the boxes to start from the center, we can reach for grid to help with that:

.container {
  background-color: #eb5bec;
  display: grid;
  height: 315px;
  place-items: center;
  position: relative;
  width: 385px;
}

If the boxes in the container are growing outward, then there’s a chance that they could expand beyond the container. Better hide any possible overflow.

.container {
  background-color: #eb5bec;
  height: 315px;
  overflow: hidden;
  position: relative;
  width: 385px;
}

I also noticed some rounded corners on it, so let’s drop that in while we’re here.

.container {
  background-color: #eb5bec;
  border-radius: 16px;
  height: 315px;
  position: relative;
  width: 385px;
}

So far, so good!

Styling the boxes

We have 10 .box elements in the markup and we want them stacked on top of one another. I started with some absolute positioning, then sized them at 100px square. Then I did the same thing with my image editing app to find the darkest color value of a box, which was #471e45.

.box {
  background: #471e45;
  height: 100px;
  position: absolute;
  width: 100px;
}

The boxes seem to fade out as they grow. That allows one box to be seen through the other, so let’s make them opaque to start.

.box {
  background: #471e45;
  height: 100px;
  opacity: 0.5;
  position: absolute;
  width: 100px;
}

Cool, cool. We’re unable to see all the boxes as they’re stacked on top of one another, but we’re making progress!

Creating the animation

Ready to write some @keyframes? We’re gonna make this super simple, going from 0 to 100% without any steps in between. We don’t even need those percentages!

@keyframes grow {
  from {
    /* do stuff */
  }
  to {
    /* do stuff */
  }
}

Specifically, we want two things to happen from start to finish:

  • The boxes go from our starting opacity value of 0.5 to 0 (fully transparent).
  • The boxes scale up to the edges of the container.
@keyframes grow {
  from {
    opacity: 0.5;
    transform: scale(0);
  }
  to {
    opacity: 0;
    transform: scale(3.85);
  }
}

How’d I land on scaling the boxes up by 3.85? Our boxes are 100px square and the container is 385px tall. A value of 3.85 gets the boxes up to 385px as they fade completely out which makes for a nice linear animation when we get there.

Speaking of which…

Applying the animation

It’s pretty easy to call the animation on our boxes. Just gotta make sure it moves in a liner timing function on an infinite basis so it’s like the Energizer Bunny and keeps going and going and going and going and…

.box {
  animation: grow 10s linear infinite; /* 10s = 10 boxes */
  /* etc. */
}

This gives us the animation we want. But! The boxes are all moving at the same time, so all we see is one giant box growing.

We’ve gotta stagger those little fellers. No loops in vanilla CSS, unfortunately, so we have to delay each box individually. We can start by setting a custom property for the delay, set it to one second, then redefine the custom property on each instance.

.box {
  --delay: 1s;
  
  animation-delay: var(--delay);
  /* same as before */
}
.box:nth-child(2) {
  --delay: 2s;
}
.box:nth-child(3) {
  --delay: 3s;
}
.box:nth-child(4) {
  --delay: 4s;
}
.box:nth-child(5) {
  --delay: 5s;
}
/* five more times... */

Huzzah!

Keep on rockin’

That’s it! We just recreated the same sort of effect used by Apple Music. There are a few finishing touches we could plop in there, like the content and whatnot. Here’s my final version again:


Recreating the Apple Music Hits Playlist Animation in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/recreating-the-apple-music-hits-playlist-animation-in-css/feed/ 4 357565
A Handy Little System for Animated Entrances in CSS https://css-tricks.com/a-handy-little-system-for-animated-entrances-in-css/ https://css-tricks.com/a-handy-little-system-for-animated-entrances-in-css/#comments Fri, 26 Nov 2021 16:12:33 +0000 https://css-tricks.com/?p=357239 I love little touches that make a website feel like more than just a static document. What if web content wouldn’t just “appear” when a page loaded, but instead popped, slid, faded, or spun into place? It might be a …


A Handy Little System for Animated Entrances in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I love little touches that make a website feel like more than just a static document. What if web content wouldn’t just “appear” when a page loaded, but instead popped, slid, faded, or spun into place? It might be a stretch to say that movements like this are always useful, though in some cases they can draw attention to certain elements, reinforce which elements are distinct from one another, or even indicate a changed state. So, they’re not totally useless, either.

So, I put together a set of CSS utilities for animating elements as they enter into view. And, yes, this pure CSS. It not only has a nice variety of animations and variations, but supports staggering those animations as well, almost like a way of creating scenes.

You know, stuff like this:

Which is really just a fancier version of this:

We’ll go over the foundation I used to create the animations first, then get into the little flourishes I added, how to stagger animations, then how to apply them to HTML elements before we also take a look at how to do all of this while respecting a user’s reduced motion preferences.

The basics

The core idea involves adding a simple CSS @keyframes animation that’s applied to anything we want to animate on page load. Let’s make it so that an element fades in, going from opacity: 0 to opacity: 1 in a half second:

.animate {
  animation-duration: 0.5s;
  animation-name: animate-fade;
  animation-delay: 0.5s;
  animation-fill-mode: backwards;
}

@keyframes animate-fade {
  0% { opacity: 0; }
  100% { opacity: 1; }
}

Notice, too, that there’s an animation-delay of a half second in there, allowing the rest of the site a little time to load first. The animation-fill-mode: backwards is there to make sure that our initial animation state is active on page load. Without this, our animated element pops into view before we want it to.

If we’re lazy, we can call it a day and just go with this. But, CSS-Tricks readers aren’t lazy, of course, so let’s look at how we can make this sort of thing even better with a system.

Fancier animations

It’s much more fun to have a variety of animations to work with than just one or two. We don’t even need to create a bunch of new @keyframes to make more animations. It’s simple enough to create new classes where all we change is which frames the animation uses while keeping all the timing the same.

There’s nearly an infinite number of CSS animations out there. (See animate.style for a huge collection.) CSS filters, like blur(), brightness() and saturate() and of course CSS transforms can also be used to create even more variations.

But for now, let’s start with a new animation class that uses a CSS transform to make an element “pop” into place.

.animate.pop {
  animation-duration: 0.5s;
  animation-name: animate-pop;
  animation-timing-function: cubic-bezier(.26, .53, .74, 1.48);
}

@keyframes animate-pop {
  0% {
    opacity: 0;
    transform: scale(0.5, 0.5);
  }

  100% {
    opacity: 1;
    transform: scale(1, 1);
  }
}

I threw in a little cubic-bezier() timing curve, courtesy of Lea Verou’s indispensable cubic-bezier.com for a springy bounce.

Adding delays

We can do better! For example, we can animate elements so that they enter at different times. This creates a stagger that makes for complex-looking motion without a complex amount of code.

This animation on three page elements using a CSS filter, CSS transform, and staggered by about a tenth of a second each, feels really nice:

All we did there was create a new class for each element that spaces when the elements start animating, using animation-delay values that are just a tenth of a second apart.

.delay-1 { animation-delay: 0.6s; }  
.delay-2 { animation-delay: 0.7s; }
.delay-3 { animation-delay: 0.8s; }

Everything else is exactly the same. And remember that our base delay is 0.5s, so these helper classes count up from there.

Respecting accessibility preferences

Let’s be good web citizens and remove our animations for users who have enabled their reduced motion preference setting:

@media screen and (prefers-reduced-motion: reduce) {
  .animate { animation: none !important; }
}

This way, the animation never loads and elements enter into view like normal. It’s here, though, that is worth a reminder that “reduced” motion doesn’t always mean “remove” motion.

Applying animations to HTML elements

So far, we’ve looked at a base animation as well as a slightly fancier one that we were able to make even fancier with staggered animation delays that are contained in new classes. We also saw how we can respect user motion preferences at the same time.

Even though there are live demos that show off the concepts, we haven’t actually walked though how to apply our work to HTML. And what’s cool is that we can use this on just about any element, whether its a div, span, article, header, section, table, form… you get the idea.

Here’s what we’re going to do. We want to use our animation system on three HTML elements where each element gets three classes. We could hard-code all the animation code to the element itself, but splitting it up gives us a little animation system we can reuse.

  • .animate: This is the base class that contains our core animation declaration and timing.
  • The animation type: We’ll use our “pop” animation from before, but we could use the one that fades in as well. This class is technically optional but is a good way to apply distinct movements.
  • .delay-<number>: As we saw earlier, we can create distinct classes that are used to stagger when the animation starts on each element, making for a neat effect. This class is also optional.

So our animated elements might now look like:

<h2 class="animate pop">One!</h2>
<h2 class="animate pop delay-1">Two!</h2>
<h2 class="animate pop delay-2">Three!</h2>

Let’s count them in!

Conclusion

Check that out: we started with a seemingly basic set of @keyframes and turned it into a full-fledged system for applying interesting animations for elements entering into view.

This is ridiculously fun, of course. But the big takeaway for me is how the examples we looked at form a complete system that can be used to create a baseline, different types of animations, staggered delays, and an approach for respecting user motion preferences. These, to me, are all the ingredients for a flexible system that’s easy to use. It gives us a lot with a little, without a bunch of extra cruft.

What we covered could indeed be a full animation library. But, of course, I didn’t stop there. I have my entire CSS file of animations in all its glory for you. There are several more types of animations in there, including 15 classes of different delays that can be used for staggering things. I’ve been using these on my own projects, but it’s still an early draft and I would love feedback on it. Please enjoy and let me know what you think in the comments!

/* ==========================================================================
Animation System by Neale Van Fleet from Rogue Amoeba
========================================================================== */
.animate {
  animation-duration: 0.75s;
  animation-delay: 0.5s;
  animation-name: animate-fade;
  animation-timing-function: cubic-bezier(.26, .53, .74, 1.48);
  animation-fill-mode: backwards;
}

/* Fade In */
.animate.fade {
  animation-name: animate-fade;
  animation-timing-function: ease;
}

@keyframes animate-fade {
  0% { opacity: 0; }
  100% { opacity: 1; }
}

/* Pop In */
.animate.pop { animation-name: animate-pop; }

@keyframes animate-pop {
  0% {
    opacity: 0;
    transform: scale(0.5, 0.5);
  }
  100% {
    opacity: 1;
    transform: scale(1, 1);
  }
}

/* Blur In */
.animate.blur {
  animation-name: animate-blur;
  animation-timing-function: ease;
}

@keyframes animate-blur {
  0% {
    opacity: 0;
    filter: blur(15px);
  }
  100% {
    opacity: 1;
    filter: blur(0px);
  }
}

/* Glow In */
.animate.glow {
  animation-name: animate-glow;
  animation-timing-function: ease;
}

@keyframes animate-glow {
  0% {
    opacity: 0;
    filter: brightness(3) saturate(3);
    transform: scale(0.8, 0.8);
  }
  100% {
    opacity: 1;
    filter: brightness(1) saturate(1);
    transform: scale(1, 1);
  }
}

/* Grow In */
.animate.grow { animation-name: animate-grow; }

@keyframes animate-grow {
  0% {
    opacity: 0;
    transform: scale(1, 0);
    visibility: hidden;
  }
  100% {
    opacity: 1;
    transform: scale(1, 1);
  }
}

/* Splat In */
.animate.splat { animation-name: animate-splat; }

@keyframes animate-splat {
  0% {
    opacity: 0;
    transform: scale(0, 0) rotate(20deg) translate(0, -30px);
    }
  70% {
    opacity: 1;
    transform: scale(1.1, 1.1) rotate(15deg);
  }
  85% {
    opacity: 1;
    transform: scale(1.1, 1.1) rotate(15deg) translate(0, -10px);
  }

  100% {
    opacity: 1;
    transform: scale(1, 1) rotate(0) translate(0, 0);
  }
}

/* Roll In */
.animate.roll { animation-name: animate-roll; }

@keyframes animate-roll {
  0% {
    opacity: 0;
    transform: scale(0, 0) rotate(360deg);
  }
  100% {
    opacity: 1;
    transform: scale(1, 1) rotate(0deg);
  }
}

/* Flip In */
.animate.flip {
  animation-name: animate-flip;
  transform-style: preserve-3d;
  perspective: 1000px;
}

@keyframes animate-flip {
  0% {
    opacity: 0;
    transform: rotateX(-120deg) scale(0.9, 0.9);
  }
  100% {
    opacity: 1;
    transform: rotateX(0deg) scale(1, 1);
  }
}

/* Spin In */
.animate.spin {
  animation-name: animate-spin;
  transform-style: preserve-3d;
  perspective: 1000px;
}

@keyframes animate-spin {
  0% {
    opacity: 0;
    transform: rotateY(-120deg) scale(0.9, .9);
  }
  100% {
    opacity: 1;
    transform: rotateY(0deg) scale(1, 1);
  }
}

/* Slide In */
.animate.slide { animation-name: animate-slide; }

@keyframes animate-slide {
  0% {
    opacity: 0;
    transform: translate(0, 20px);
  }
  100% {
    opacity: 1;
    transform: translate(0, 0);
  }
}

/* Drop In */
.animate.drop { 
  animation-name: animate-drop; 
  animation-timing-function: cubic-bezier(.77, .14, .91, 1.25);
}

@keyframes animate-drop {
0% {
  opacity: 0;
  transform: translate(0,-300px) scale(0.9, 1.1);
}
95% {
  opacity: 1;
  transform: translate(0, 0) scale(0.9, 1.1);
}
96% {
  opacity: 1;
  transform: translate(10px, 0) scale(1.2, 0.9);
}
97% {
  opacity: 1;
  transform: translate(-10px, 0) scale(1.2, 0.9);
}
98% {
  opacity: 1;
  transform: translate(5px, 0) scale(1.1, 0.9);
}
99% {
  opacity: 1;
  transform: translate(-5px, 0) scale(1.1, 0.9);
}
100% {
  opacity: 1;
  transform: translate(0, 0) scale(1, 1);
  }
}

/* Animation Delays */
.delay-1 {
  animation-delay: 0.6s;
}
.delay-2 {
  animation-delay: 0.7s;
}
.delay-3 {
  animation-delay: 0.8s;
}
.delay-4 {
  animation-delay: 0.9s;
}
.delay-5 {
  animation-delay: 1s;
}
.delay-6 {
  animation-delay: 1.1s;
}
.delay-7 {
  animation-delay: 1.2s;
}
.delay-8 {
  animation-delay: 1.3s;
}
.delay-9 {
  animation-delay: 1.4s;
}
.delay-10 {
  animation-delay: 1.5s;
}
.delay-11 {
  animation-delay: 1.6s;
}
.delay-12 {
  animation-delay: 1.7s;
}
.delay-13 {
  animation-delay: 1.8s;
}
.delay-14 {
  animation-delay: 1.9s;
}
.delay-15 {
  animation-delay: 2s;
}

@media screen and (prefers-reduced-motion: reduce) {
  .animate {
    animation: none !important;
  }
}

A Handy Little System for Animated Entrances in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/a-handy-little-system-for-animated-entrances-in-css/feed/ 18 357239
Intrinsic Typography is the Future of Styling Text on the Web https://css-tricks.com/intrinsic-typography-is-the-future-of-styling-text-on-the-web/ https://css-tricks.com/intrinsic-typography-is-the-future-of-styling-text-on-the-web/#comments Tue, 20 Apr 2021 14:24:44 +0000 https://css-tricks.com/?p=337892 The way we style text hasn’t changed much over the years. There have been numerous advancements to help make things more flexible, like layouts, but in terms of styling, most finite aspects of our designs, like text, remain relatively unchanged. …


Intrinsic Typography is the Future of Styling Text on the Web originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
The way we style text hasn’t changed much over the years. There have been numerous advancements to help make things more flexible, like layouts, but in terms of styling, most finite aspects of our designs, like text, remain relatively unchanged. This is especially true of text styling. We write code to style text explicitly for every portion of our layouts, and then, to make it responsive, we write more code to make it work at every breakpoint. This means that, as different areas of text compress and expand, the result is tension — palpable, experiential tension — just before the content breaks. At these places, content suffers from not being sized or spaced well, all the while being supported by overly complicated and brittle code.

Intrinsic typography shifts all this, clearing it away by starting at the code itself to affect the styling. Instead of writing explicit text styles, you define how those styles change in proportion to the text’s area. This enables you to use more flexible text components in more layout variations. It simplifies your code, increasing the opportunities for new layout possibilities. Intrinsic typography works so that text self-adjusts to the area in which it’s rendered. Instead of sizing and spacing text for each component at every breakpoint, the text is given instructions to respond to the areas it is placed in. As a result, intrinsic typography enables designs to be far more flexible, adapting to the area in which it is placed, with far less code.

Typographic superpowers beyond clamp()

The result of using intrinsic typography goes well beyond what is possible with tools like clamp(). Intrinsic typographic styling blends the component portability of element queries with the interpolation control of CSS animations, enabling seamless changes of any value across container widths. This technique enables things that aren’t possible with other CSS techniques, such as fluidly adjusting variable font settings, color, and unitless line-height as an element’s area changes. You also avoid the accessibility pitfalls of clamp() and locks where changing the browser’s default font size shifts your typography out of alignment with your breakpoints when using relative units.

How is this different from responsive typography?

Responsive typography references the viewport to transform text. It does this through media queries, clamp(), or CSS Locks. While these techniques enable granular control of typography across screen sizes, they lack the ability to control typography in different components. This means that, for a page with an array of differently sized content areas, a new headline style would need to be created for each of these areas with a responsive typography approach.

Intrinsic typography doesn’t need all that. With intrinsic typography, a single headline style can be used in all different content areas. Discrete headline styles can be consolidated into one intrinsic headline. This is a distinction similar to that of element queries versus media queries: with element queries it’s possible to bind all of the scaling information to a component, where media queries the styles always reference the viewport.

A series of entries scaling proportionally to the container they are rendered in. The font in this demo is Obviously by OHno Type Co.

The anatomy of an intrinsic style

If we were to take the intrinsic headline styles above and extrude out all the variations within them, it would look like the following:

An extrapolation of an intrinsic style along the Z-axis. As the width of an area of text changes, different cross sections of this extrapolation are used as the styles.

Within larger areas of the page, the text is typeset to be bigger, bolder, and wider. In smaller areas of the page the text is smaller, lighter, and narrower. The area in which a headline is rendered is measured, and then the appropriate slice is taken from this intrinsic headline style to be used for that specific headline.

You may notice a few things about the shape of this extruded headline style. The text goes from being smaller to larger, but the shape itself has curves. This control over how text scales from one point to another is particularly useful as screens get smaller to ensure optimal legibility. Below you can see the same set of styles being applied to two columns of text, one with a curved shape and one with a linear shape. In the curved intrinsic example the text is vastly more legible in more places, in comparison to the example using linear interpolation, where the text becomes too small too quickly.

Two panels of text that share the same start and end styles. However on the right, the styles are interpolated using a Bézier curve to optimize legibility at all sizes.

Through combining the ability to interpolate text styling across sizes and areas of a layout as well as shaping how those settings are interpolated, intrinsic typography gives designers an unprecedented amount of control over how text is rendered at any screen or component size.

Typeset intrinsically

Typetura developed a tool to add intrinsic typesetting functionality to CSS (I’m the creator.) This tool enables the necessary typographic styles to be written, injecting flexibility where previously there was none. Intrinsic styles are stored in CSS keyframes and change based on the width of a parent element. This enables interpolation of any animatable property across element widths. To reference back to our element queries example, think interpolated element queries.

To set up your keyframes, 0% is equal to a container width of 0px, and keyframe 100% is the maximum container width your styles will cover. This value is 1600px by default. Containers can be defined by adding the class typetura to an element, with the root element as the default container. Child elements will be styled based on the parent context’s width, unless a new context is defined.

@keyframes headline {
  0% {
    font-size: 1rem;
  }
  100% {
    font-size: 4rem;
  }
}

To attach these styles to your element, use the custom property --tt-key. Now you can see your first intrinsic style.

@keyframes headline {
  0% {
    font-size: 1rem;
    line-height: 1.1;
  }
  100% {
    font-size: 4rem;
    line-height: 1;
  }
}

.headline {
  --tt-key: headline;
}

To shape how these styles scale, use the custom property --tt-ease. This property accepts CSS easing functions and keywords. This enables you to rapidly bring up your base font size or taper off headline scaling and spacing. Additionally, we can constrain the range these styles cover with --tt-max to better fit the constraints of your layouts and what the text is used for.

@keyframes headline {
  0% {
    font-size: 1rem;
    line-height: 1.1;
  }
  100% {
    font-size: 4rem;
    line-height: 1;
  }
}

.headline {
  --tt-key: headline;
  --tt-max: 600;
  --tt-ease: ease-in-out;
}

The following example shows how flexible your page can be when all the text on it is driven by intrinsic typographic styles; from the root of the document and up. The text can seamlessly transition from a monitor serving a conference room all the way down to the size of a watch — all without media queries. Text styles can also be shared in different modules; for example, the headline at the top of the page and headlines in the next-click area are all driven by the same style. While efficiencies appear immediately at any size of website, they quickly compound: the larger site you have, the more these efficiencies build.

Editorial page demo at a desktop screen size
Editorial page demo at a small tablet screen size
Editorial page demo at a small phone or watch screen size, and no text is getting cut off

Check out this Pen. In it, I’ve added an intrinsic style inspector so you can click on each headline and see what the rendered size is. Within the inspector you can also manipulate the shape of the intrinsic style, and the upper boundary. This allows you to begin to see the typographic styling possibilities for enabled by Typetura.

Intrinsic Typography is the future of styling on the web

Baking these design rules into your content is the practice of intrinsic design, and baking these rules into your text is the practice of intrinsic typography. Intrinsic web design, coined by Jen Simmons, is a concept where common design mutations are baked into the very fabric of our components. Instead of explicitly stating the style of each individual piece of content, intrinsic layouts are given design constraints and our content responds to its environment, as opposed to explicitly defining styles. This approach both simplifies your codebase and enhances the flexibility of your designs, as components have instructions that help them respond to more than just the viewport.

Typetura brings this philosophy into text styling. With text components being our most foundational design material, a material that is reused in almost every component, intrinsic typography has significant advantages over other methodologies. Advantages of design resilience, scalability, and simplification of code exist deeper in your project and extend its lifespan. Scale down to the size of a watch or up to the size of a TV, and where text once limited how far your layout could reach, it now supports your ambitions.


Intrinsic Typography is the Future of Styling Text on the Web originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/intrinsic-typography-is-the-future-of-styling-text-on-the-web/feed/ 16 337892
CSS Switch-Case Conditions https://css-tricks.com/css-switch-case-conditions/ https://css-tricks.com/css-switch-case-conditions/#comments Wed, 17 Feb 2021 16:05:26 +0000 https://css-tricks.com/?p=334015 CSS is yet to have a switch rule or conditional if, aside from the specific nature of @media queries and some deep trickery with CSS custom properties. Let’s have a look at why it would be useful if we …


CSS Switch-Case Conditions originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
CSS is yet to have a switch rule or conditional if, aside from the specific nature of @media queries and some deep trickery with CSS custom properties. Let’s have a look at why it would be useful if we did, and look at a trick that is usable today for pulling it off.

Recent chatter about the possibility

While none of these things are usable today, there has been a good amount of chat about the concept of generic conditional CSS just in the last year:

So, yes. The demand for conditional CSS is there.

Imagine why conditional CSS would be useful

Perhaps a visual change after a certain amount of scrolling. A visual change after a numeric input is within a certain range. A component with a handful of states.

There is a whole genre of extremely popular JavaScript libraries for UI (e.g. React, Vue, etc.) that are essentially for building UI based on state. Clearly this is a developer need. If we could move that state-based styling to CSS, that’s all the less JavaScript we might need — and maybe a better separation of concerns.

A common theme

We already have custom properties in CSS, and we could base state-change logic on them, changing a block of styles as a side effect of the custom property changing to certain values.

It’s true that we have mechanisms for changing blocks of styles already. We can change class through JavaScript, and that class can apply whatever we like in CSS. But that doesn’t mean state-based styling in CSS wouldn’t be useful. We don’t always have the ability or may not want to write any JavaScript for this, and instead change custom properties in other ways (e.g. media queries, HTML changes, etc). Doing it in CSS means helping separate business logic and visual style logic.

A trick! Using @keyframes for state

CSS @keyframes can be used to switch specific changes. Through the power of the animation property, a possibility opens up to select exactly which frame to show, and have it pause exactly on that frame, effectively mimicking a switch-case statement or state-based styles.

Let’s see see this in action by playing with the animation-delay property:

Here’s what’s happening in that Pen:

  • animation-delay: Negative delay values force a specific frame (or between) to take effect (positive values don’t work that way). We’ll use this trick to force states.
  • animation-play-state: paused: We’re not actually animating anything, so the animation will stay paused.
  • animation-duration: The actual duration doesn’t matter, it just needs one so there is a time span to hold the different keyframes. We’ll make it a value like 100.001s so that if we delay by 100s, the last keyframe will still work. The duration needs to be longer than the delay value.

The first range input modifies the animation-delay between a range of -100s and 0s.

A real-world use-case

Before we jump straight into the working example, it’s worth discussing this trick in more detail because there’s some nuances you ought to be aware of.

First off, the trick only works with numeric values. So, color values or strings because it’s strictly performing math.

Second, there’s the boolean trick. Consider a variable --value: 10 which can take any numeric value between 0 and 100. We want to apply color if the value is above 5. How do we know if the value is over or below 5? And even if we do know, how does that help actually help us?

--is-above-5: clamp(0, var(--value) - 5, 1)
--value--is-above-5Result
000 – 5 = -5, clamp() forces a value no less than 0
202 – 5 = -3, clamp() forces a value no less than 0
505 – 5 = 0
717 – 5 = 2, clamp() forces a value no larger than 1

clamp() is like a smarter calc(), in that it allows us to strictly confine a computed value to range while declaring an ideal value. That range is all that is needed to achieve a boolean variable.

Write any math in the second parameter of the clamp() and that will either output 0 (or below) or 1 (or above). Make sure not to write any math that might result in a number between 0 and 1.

Here’s how that works out:

The range input’s only job is to “broadcast” its value by defining a values for --value, --min and --max, then modifying the --value using an oninput event. That is the most minimal thing that can be done get state-like behavior in CSS. No JavaScript needed.

Using CSS math functions, it is possible to infer the “completed” percentage of the progress bar from those same variables:

--completed: calc((var(--value) - var(--min) ) / (var(--max) - var(--min)) * 100);

Now, we know if the value is over a certain percentage, giving us yet another way to make changes by state:

--over-30: clamp(0, var(--completed) - 30, 1);
--over-70: clamp(0, var(--completed) - 70, 1);
/* ...and so on... */

OK, great, but how can we use this to select a specific keyframe? By using max() function:

--frame: max( 
    calc(1 - var(--over-30)), 
    var(--over-30) * 2, 
    var(--over-70) * 3, 
    var(--is-100)  * 4 
);

The thing with CSS booleans is that there are many ways to use them to achieve a certain goal, and one must get creative, finding a formula which is short and readable.

In the above formula, the booleans will “toggle” a frame number if the boolean has the value of 1. Since we are using a max function, the the largest toggled frame number will be the computed value of --frame.

Note that the color change has a slight transition. We could have done this with the background: currentColor; on the fill area, which inherits the color from the parent, but I chose to use CSS Houdini to illustrate the power of assigning transitions to CSS variables by declaring its type.

An example of a heavily-used CSS boolean trick can be viewed in the below Pen, which is a CSS-only component with lots of variables that allow a wide range of customization:

I am sure there are many other use cases for this little trick and am excited to see what else might be achieved by the creativity of the community.


CSS Switch-Case Conditions originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/css-switch-case-conditions/feed/ 5 334015
How to Play and Pause CSS Animations with CSS Custom Properties https://css-tricks.com/how-to-play-and-pause-css-animations-with-css-custom-properties/ https://css-tricks.com/how-to-play-and-pause-css-animations-with-css-custom-properties/#comments Thu, 21 Jan 2021 15:36:24 +0000 https://css-tricks.com/?p=332679 Let’s have a look CSS @keyframes animations, and specifically about how you can pause and otherwise control them. There is a CSS property specifically for it, that can be controlled with JavaScript, but there is plenty of nuance to get …


How to Play and Pause CSS Animations with CSS Custom Properties originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Let’s have a look CSS @keyframes animations, and specifically about how you can pause and otherwise control them. There is a CSS property specifically for it, that can be controlled with JavaScript, but there is plenty of nuance to get into in the details. We’ll also look at my preferred way of setting this up which gives lots of control. Hint: it involves CSS custom properties.

The importance of pausing animations

Recently, while working on the CSS-powered slideshow you’ll see later in this article, I was inspecting the animations in the Layers panel of DevTools. I noticed something interesting I’d never thought about before: animations not currently in the viewport were still running!

Maybe it’s not that unexpected. We know videos do that. Videos just go on until you pause them. But it made me wonder if these playing animations still use the CPU/GPU? Do they consume unnecessary processing power, slowing down other parts of the page?

Inspecting frames in the Performance panel in DevTools didn’t shed any more light on this since I couldn’t see “offscreen”-frames. But, when I scrolled away from my “CSS Only Slideshow” at the first slide, then waited and scrolled back, it was at slide five. The animation hadn’t paused. Animations just run and run, until you pause them.

So I began to look into how, why and when animations should pause. Performance is an obvious reason, given the findings above. Another reason is control. Users not only love to have control, but they should have control. A couple of years ago, my wife had a really bad concussion. Since then, she has avoided webpages with too many animations, as they make her dizzy. As a result, I consider accessibility perhaps the most important reason for allowing animations to pause.

All together, this is important stuff. We’re talking specifically about CSS keyframe animations, but broadly, that means we’re talking about:

  1. Performance
  2. Control
  3. Accessibility

The basics of pausing an animation

The only way to truly pause an animation in CSS is to use the animation-play-state property with a paused value.

.paused {
  animation-play-state: paused;
}

In JavaScript, the property is “camelCased” as animationPlayState and set like this:

element.style.animationPlayState = 'paused';

We can create a toggle that plays and pauses the animation by reading the current value of animationPlayState:

const running = element.style.animationPlayState === 'running';

…and then setting it to the opposite value:

element.style.animationPlayState = running ? 'paused' : 'running';

Setting the duration

Another way to pause animations is to set animation-duration to 0s. The animation is actually running, but since it has no duration, you won’t see any action.

But if we change the value to 3s instead:

It works, but has a major caveat: the animations are technically still running. The animation is merely toggling between its initial position, and where it is next in the sequence.

Straight up removing the animation

We can remove the animation entirely and add it back via classes, but like animation-duration, this doesn’t actually pause the animation.

.remove-animation {
  animation: none !important;
}

Since true pausing is really what we’re after here, let’s stick with animation-play-state and look into other ways of using it.

Using data attributes and CSS custom properties

Let’s use a data-attribute as a selector in our CSS. We can call those whatever we want, so I’m going to use a [data-animation]-attribute on all the elements where I’d like to play/pause animations. That way, it can be distinguished from other animations:

<div data-animation></div>

That attribute is the selector, and the animation shorthand is the property where we’re setting everything. We’ll toss in a bunch of CSS custom properties *(*using Emmet-abbreviations) as values:

[data-animation] {
  animation:
    var(--animn, none)
    var(--animdur, 1s)
    var(--animtf, linear)
    var(--animdel, 0s)
    var(--animic, infinite)
    var(--animdir, alternate)
    var(--animfm, none)
    var(--animps, running);
}

With that in place, any animation with this data-attribute will be perfectly ready to accept animations, and we can control individual aspects of the animation with custom properties. Some animations are going to have something in common (like duration, easing-type, etc.), so fallback values are set on the custom properties as well.

Why CSS custom properties? First of all, they can be read and set in both CSS and JavaScript. Secondly, they help significantly reduce the amount of CSS we need to write. And, since we can set them within @keyframes (at least in Chrome at the time of writing), they offer new and exiting ways to work with animations!

For the animations themselves, I’m using class selectors and updating the variables from the [data-animation]-selector:

<div class="circle a-slide" data-animation></div>

Why a class and a data-attribute? At this stage, the data-animation attribute might as well be a regular class, but we’re going to use it in more advanced ways later. Note that the .circle class name actually has nothing to do with the animation — it’s just a class for styling the element.

/* Animation classes */
.a-pulse {
  --animn: pulse;
}
.a-slide {
  --animdur: 3s;
  --animn: slide;
}

/* Keyframes */
@keyframes pulse {
  0% { transform: scale(1); }
  25% { transform: scale(.9); }
  50% { transform: scale(1); }
  75% { transform: scale(1.1); }
  100% { transform: scale(1); }
}
@keyframes slide {
  from { margin-left: 0%; }
  to { margin-left: 150px; }
}

We only need to update the values that will change, so if we use some common values in the fallback values for the data-animation selector, we only need to update the name of the animation’s custom property, --animn.

Example: Pausing with the checkbox hack

To pause all the animations using the ol’ checkbox hack, let’s create a checkbox before the animations:

<input type="checkbox" data-animation-pause />

And update the --animps property when checked:

[data-animation-pause]:checked ~ [data-animation] {
  --animps: paused;
}

That’s it! The animations toggle between played and paused when clicking the checkbox — no JavaScript required.

CSS-only slideshow

Let’s put some of these ideas to work!

I‘ve played with the <details>-tag a lot recently. It’s the obvious candidate for accordions, but it can also be used for tooltips, toggle-tips, drop-downs (styled <select>-look-a-likes), mega-menus… you name it. It is the official HTML disclosure element, after all. Apart from the global attributes and global events that all HTML elements accept, <details> has a single open attribute, and a single toggle event. So, like the checkbox hack, it’s perfect for toggling state — but even simpler:

details[open] {
  --state: 1;
}
details:not([open]) {
  --state: 0;
}

I decided to do a slideshow, where the slides change automatically via a primary animation called autoplay, and each individual slide has its own unique secondary animation. The animation-play-state is controlled by the --animps-property. Each individual slide can have it’s own, unique animation, defined in a --animn-property:

<figure style="--animn:kenburns-top;--index:0;">
  <img src="some-slide-image.jpg" />
  <figcaption>Caption</figcaption>
</figure>

The animation-play-state of the secondary animations are controlled by the --img-animps-property. I found a bunch of nice Ken Burns-esque animations at Animista and switched between them in the --animn-properties of the slides.

Pausing an animation from another animation

In order to prevent GPU overload, it would be ideal for the primary animation to pause any secondary animations. We noted it briefly earlier, but only Chrome (at the time of writing, and it is a bit shaky) can update a CSS Custom Property from an @keyframe animation — which you can see in the following example where the --bgc-property and --counter-properties are modified at different frames:

The initial state of the secondary animation, the --img-animps -property, needs to be paused, even if the primary animation is running:

details[open] ~ .c-mm__inner .c-mm__frame {
  --animps: running;
  --img-animps: paused;
}

Then, in the main animation @keyframes, the property is updated to running:

@keyframes autoplay {
  0.1% {
    --img-animps: running; /* START */
    opacity: 0;
    z-index: calc(var(--z) + var(--slides))
  }
  5% { opacity: 1 }
  50% { opacity: 1 }
  51% { --img-animps: paused } /* STOP! */
  100% {
    opacity: 0;
    z-index: var(--z)
  }
}

To make this work in browsers other than Chrome, the initial value needs to be running, as they cannot update a CSS custom property from a @keyframe.

Here’s the slideshow, with a “details hack” play/pause-button — no JavaScript required:

Enabling prefers-reduced-motion

Some people prefer no animations, or at least reduced motion. It might just be a personal preference, but can also be because of a medical condition. We talked about the importance of accessibility with animations at the very top of this post.

Both macOS and Windows have options that allow users to inform browsers that they prefer reduced motion on websites. This enables us to reach for the prefers-reduced-motion feature query, which Eric Bailey has written all about.

@media (prefers-reduced-motion) { ... }

Let’s use the [data-animation]-selector for reduced motion by giving it different values that are applied when prefers-reduced-motion is enabled*:*

  • alternate = run a different animation
  • once = set the animation-iteration-count to 1
  • slow = change the animation-duration-property
  • stop = set animation-play-state to paused

These are just suggestions and they can be anything you want, really.

<div class="circle a-slide" data-animation="alternate"></div>
<div class="circle a-slide" data-animation="once"></div>
<div class="circle a-slide" data-animation="slow"></div>
<div class="circle a-slide" data-animation="stop"></div>

And the updated media query:

@media (prefers-reduced-motion) {
  [data-animation="alternate"] {
   /* Change animation duration AND name */
    --animdur: 4s;
    --animn: opacity;
  }
  [data-animation="slow"] {
    /* Change animation duration */
    --animdur: 10s;
  }
  [data-animation="stop"] {
    /* Stop the animation */
    --animps: paused;
  }
}

If this is too generic, and you prefer having unique, alternate animations per animation class, group the selectors like this:

.a-slide[data-animation="alternate"] { /* etc. */ }

Here’s a Pen with a checkbox simulating prefers-reduced-motion. Scroll down within the Pen to see the behavior change for each circle:

Pausing with JavaScript

To re-create the “Pause all animations”-checkbox in JavaScript, iterate all the [data-animation]-elements and toggle the same --animps custom property:

<button id="js-toggle" type="button">Toggle Animations</button>
const animations = document.querySelectorAll('[data-animation');
const jstoggle = document.getElementById('js-toggle');

jstoggle.addEventListener('click', () => {
  animations.forEach(animation => {
    const running = getComputedStyle(animation).getPropertyValue("--animps") || 'running';
    animation.style.setProperty('--animps', running === 'running' ? 'paused' : 'running');
  })
});

It’s exactly the same concept as the checkbox hack, using the same custom property: --animps, only set by JavaScript instead of CSS. If we want to support older browsers, we can toggle a class, that will update the animation-play-state.

Using IntersectionObserver

To play and pause all [data-animation]-animations automatically — and thus not unnecessarily overloading the GPU — we can use an IntersectionObserver.

First, we need to make sure that no animations are running at all:

[data-animation] {
  /* Change 'running' to 'paused' */
  animation: var(--animps, paused); 
}

Then, we’ll create the observer and trigger it when an element is 25% or 75% in viewport. If the latter is matched, the animation starts playing; otherwise it pauses.

By default, all elements with a [data-animation]-attribute will be observed, but if prefers-reduced-motion is enabled (set to “reduce”), the elements with [data-animation="stop"] will be ignored.

const IO = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      const state = (entry.intersectionRatio >= 0.75) ? 'running' : 'paused';
      entry.target.style.setProperty('--animps', state);
    }
  });
}, {
  threshold: [0.25, 0.75]
});

const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
const elements = mediaQuery?.matches ? document.querySelectorAll(`[data-animation]:not([data-animation="stop"]`) : document.querySelectorAll('[data-animation]');

elements.forEach(animation => {
  IO.observe(animation);
});

You have to play around with the threshold-values, and/or whether you need to unobserve some animations after they’ve triggered, etc. If you load new content or animations dynamically, you might need to re-write parts of the observer as well. It’s impossible to cover all scenarios, but using this as a foundation should get you started with auto-playing and pausing CSS animations!

Bonus: Adding <audio> to the slideshow with minimal JavaScript

Here’s an idea to add music to the slideshow we built. First, add an audio-tag:

<audio src="/asset/audio/slideshow.mp3" hidden loop></audio>

Then, in Javascript:

const audio = document.querySelector('your-audio-selector');
const details = document.querySelector('your-details-selector');
details.addEventListener('toggle', () => {
  details.open ? audio.play() : audio.pause();
})

Pretty simple, huh?

I did a “Silent Movie” (with audio)-demo here, where you get to know my geeky past. 🙂


How to Play and Pause CSS Animations with CSS Custom Properties originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/how-to-play-and-pause-css-animations-with-css-custom-properties/feed/ 3 332679
Mixing Colors in Pure CSS https://css-tricks.com/mixing-colors-in-pure-css/ https://css-tricks.com/mixing-colors-in-pure-css/#comments Mon, 16 Nov 2020 15:21:46 +0000 https://css-tricks.com/?p=325117 Red + Blue = Purple… right?

Is there some way to express that in CSS? Well, not easily. There is a proposal draft for a color-mix function and some degree of interest from Chrome, but it doesn’t seem right around …


Mixing Colors in Pure CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Red + Blue = Purple… right?

Is there some way to express that in CSS? Well, not easily. There is a proposal draft for a color-mix function and some degree of interest from Chrome, but it doesn’t seem right around the corner. It would be nice to have native CSS color mixing, as it would give designers greater flexibility when working with colors. One example is to create tinted variants of a single base color to form a design palette.

But this is CSS-Tricks so let’s do some CSS tricks.

We have a calc() function in CSS for manipulating numbers. But we have very few ways to operate directly on colors, even though some color formats (e.g. hsl() and rgb()) are based on numeric values.

Mixing colors with animation

We can transition from one color to another in CSS. This works:

div {
  background: blue;
  transition: 0.2s;
}
div:hover {
  background: red; 
}

And here’s that with animations:

div {
  background: blue;
  transition: 0.2s;
}
div:hover {
  animation: change-color 0.2s forwards;
}


@keyframes change-color {
  to {
    background: red;
  }
}

This is an keyframe animation that runs infinitely, where you can see the color moving between red and blue. Open the console and click the page — you can see that even JavaScript can tell you the current color at any exact point in the animation.

So what if we pause the animation somewhere in the middle? Color mixing works! Here is a paused animation that is 0.5s through it’s 1s duration, so exactly halfway through:

We accomplished that by setting an animation-delay of -0.5s. And what color is halfway between red and blue? Purple. We can adjust that animation-delay to specify the percentage of two colors.

This works for Chromium core browsers and Firefox. In Safari, you must change animation-name to force browser to recalculate the animation progress.

This trick can also be used for adding alpha channel to a color, which is typically useful for theming.

Getting the mixed color to a CSS custom property

This is a neat trick so far, but it’s not very practical to apply an animation on any element you need to use a mixed color on, and then have to set all the properties you want to change within the @keyframes.

We can improve on this a smidge if we add in a couple more CSS features:

  1. Use a @property typed CSS custom property, so it can be created as a proper color, and thus animated as a color.
  2. Use a Sass @function to easily call keyframes at a particular point.

Now we still need to call animation, but the result is that a custom property is altered that we can use on any other property.


Mixing Colors in Pure CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/mixing-colors-in-pure-css/feed/ 2 325117
Animating Number Counters https://css-tricks.com/animating-number-counters/ https://css-tricks.com/animating-number-counters/#comments Fri, 09 Oct 2020 14:46:10 +0000 https://css-tricks.com/?p=322391 Number animation, as in, imagine a number changing from 1 to 2, then 2 to 3, then 3 to 4, etc. over a specified time. Like a counter, except controlled by the same kind of animation that we use for …


Animating Number Counters originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Number animation, as in, imagine a number changing from 1 to 2, then 2 to 3, then 3 to 4, etc. over a specified time. Like a counter, except controlled by the same kind of animation that we use for other design animation on the web. This could be useful when designing something like a dashboard, to bring a little pizazz to the numbers. Amazingly, this can now be done in CSS without much trickery. You can jump right to the new solution if you like, but first let’s look at how we used to do it.

One fairly logical way to do number animation is by changing the number in JavaScript. We could do a rather simple setInterval, but here’s a fancier answer with a function that accepts a start, end, and duration, so you can treat it more like an animation:

Keeping it to CSS, we could use CSS counters to animate a number by adjusting the count at different keyframes:

Another way would be to line up all the numbers in a row and animate the position of them only showing one at a time:

Some of the repetitive code in these examples could use a preprocessor like Pug for HTML or SCSS for CSS that offer loops to make them perhaps easier to manage, but use vanilla on purpose so you can see the fundamental ideas.

The New School CSS Solution

With recent support for CSS.registerProperty and @property, we can animate CSS variables. The trick is to declare the CSS custom property as an integer; that way it can be interpolated (like within a transition) just like any other integer.

@property --num {
  syntax: '<integer>';
  initial-value: 0;
  inherits: false;
}

div {
  transition: --num 1s;
  counter-reset: num var(--num);
}
div:hover {
  --num: 10000;
}
div::after {
  content: counter(num);
}

Important Note: At the time of this writing, this @property syntax is only supported in Chrome ( and other Chromium core browsers like Edge and Opera), so this isn’t cross-browser friendly. If you’re building something Chrome-only (e.g. an Electron app) it’s useful right away, if not, wait. The demos from above are more widely supported.

The CSS content property can be used to display the number, but we still need to use counter to convert the number to a string because content can only output <string> values.

See how we can ease the animations just like any other animation? Super cool! 

Typed CSS variables can also be used in @keyframes

One downside? Counters only support integers. That means decimals and fractions are out of the question. We’d have to display the integer part and fractional part separately somehow.

Can we animate decimals?

It’s possible to convert a decimal (e.g. --number) to an integer. Here’s how it works:

  1. Register an <integer> CSS variable ( e.g. --integer ), with the initial-value specified
  2. Then use calc() to round the value: --integer: calc(var(--number))

In this case, --number will be rounded to the nearest integer and store the result into --integer.

@property --integer {
  syntax: "<integer>";
  initial-value: 0;
  inherits: false;
}
--number: 1234.5678;
--integer: calc(var(--number)); /* 1235 */

Sometimes we just need the integer part. There is a tricky way to do it: --integer: max(var(--number) - 0.5, 0). This works for positive numbers. calc() isn’t even required this way.

/* @property --integer */
--number: 1234.5678;
--integer: max(var(--number) - 0.5, 0); /* 1234 */

We can extract the fractional part in a similar way, then convert it into string with counter (but remember that content values must be strings). To display concatenated strings, use following syntax:

content: "string1" var(--string2) counter(--integer) ...

Here’s a full example that animates percentages with decimals:

Other tips

Because we’re using CSS counters, the format of those counters can be in other formats besides numbers. Here’s an example of animating the letters “CSS” to “YES”!

Oh and here’s another tip: we can debug the values grabbing the computed value of the custom property with JavaScript:

getComputedStyle(element).getPropertyValue('--variable')

That’s it! It’s amazing what CSS can do these days.


Animating Number Counters originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/animating-number-counters/feed/ 27 322391
A New Way to Delay Keyframes Animations https://css-tricks.com/a-new-way-to-delay-keyframes-animations/ https://css-tricks.com/a-new-way-to-delay-keyframes-animations/#comments Tue, 02 Jun 2020 14:45:22 +0000 https://css-tricks.com/?p=311206 If you’ve ever wanted to add a pause between each iteration of your CSS @keyframes animation, you’ve probably been frustrated to find there’s no built-in way to do it in CSS. Sure, we can delay the start of a set …


A New Way to Delay Keyframes Animations originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
If you’ve ever wanted to add a pause between each iteration of your CSS @keyframes animation, you’ve probably been frustrated to find there’s no built-in way to do it in CSS. Sure, we can delay the start of a set of @keyframes with animation-delay, but there’s no way to add time between the first iteration through the keyframes and each subsequent run. 

This came up when I wanted to adapt this shooting stars animation for use as the background of the homepage banner in a space-themed employee portal. I wanted to use fewer stars to reduce distraction from the main content, keep CPUs from melting, and still have the shooting stars seem random.

No pausing

For comparisons sake.

The “original” delay method

Here’s an example of where I applied the traditional keyframes delay technique to my fork of the shooting stars.

This approach involves figuring out how long we want the delay between iterations to be, and then compressing the keyframes to a fraction of 100%. Then, we maintain the final state of the animation until it reaches 100% to achieve the pause.

@keyframes my-animation {
  /* Animation happens between 0% and 50% */
  0% {
    width: 0;
  }
  15% {
    width: 100px;
  }
  /* Animation is paused/delayed between 50% and 100% */
  50%, 100% {
    width: 0;
  }
}

I experienced the main drawback of this approach: each keyframe has to be manually tweaked, which is mildly painful and certainly prone to error. It’s also harder to understand what the animation is doing if it requires mentally transposing all the keyframes back up to 100%.

New technique: hide during the delay

Another technique is to create a new set of @keyframes that is responsible for hiding the animation during the delay. Then, apply that with the original animation, at the same time.

.target-of-animation {
  animation: my-awesome-beboop 1s, pause-between-iterations 4s;
}

@keyframes my-awesome-beboop {
  ...
}

@keyframes pause-between-iterations {
  /* Other animation is visible for 25% of the time */
  0% {
    opacity: 1;
  }
  25% {
    opacity: 1;
  }
  /* Other animation is hidden for 75% of the time */
  25.1% {
    opacity: 0;	
  }
  100% {
    opacity: 0;
  }
}

A limitation of this technique is that the pause between animations must be an integer multiple of the “paused” keyframes. That’s because keyframes that repeat infinitely will immediately execute again, even if there are longer running keyframes being applied to the same element.

Interesting aside: When I started this article, I mistakenly thought that an easing function is applied at 0% and ends at 100%.. Turns out that the easing function is applied to each CSS property, starting at the first keyframe where a value is defined and ending at the next keyframe where a value is defined (e.g., an easing curve would be applied from 25% to 75%, if the keyframes were 25% { left: 0 } 75% { left: 50px}). In retrospect, this totally makes sense because it would be hard to adjust your animation if it was a subset of the total easing curve, but my mind is slightly blown.

In the my-awesome-beboop keyframes example above, my-awesome-beboop will run three times behind the scenes during the pause-between-animations keyframes before being revealed for what appears to be its second loop to the user (which is really the fifth time it’s been executed).  

Here’s an example that uses this to add a delay between the shooting stars:

 

Can’t hide your animation during the delay?

If you need to keep your animation on screen during the delay, there is another option besides hiding. You can still use a second set of @keyframes, but animate a CSS property in a way that counteracts or nullifies the motion of the primary animation. For example, if your main animation uses translateX, you can animate left or margin-left in your set of delay @keyframes.

Here’s a couple of examples:

Pause by changing transform-origin:

Pause by counter-acting transform: translateX by animating the left property:

In the case of the pausing the translateX animation, you’ll need to get fancier with the @keyframes if you need to pause the animation for more than just a single iteration:

/* pausing the animation for three iterations */
@keyframes slide-left-pause {
  25%, 50%, 75% {
    left: 0;
  }
  37.5%, 62.5%, 87.5% {
    left: -100px;
  }
  100% {
    left: 0;
  }
}

You may get some slight jitter during the pause. In the translateX example above, there’s some minor vibration on the ball during the slide-left-pause as the animations fight each other for dominance.

Wrap up

The best option performance-wise is to hide the element during the delay or animate transform. Animating properties like left, margin, width are much more intense on a processor than animating opacity (although the contain property appears to be changing that).

If you have any insights or comments on this idea, let me know!


Thanks to Yusuke Nakaya for the original shooting stars CSS animation that I forked on CodePen.


A New Way to Delay Keyframes Animations originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/a-new-way-to-delay-keyframes-animations/feed/ 3 311206
Performant Expandable Animations: Building Keyframes on the Fly https://css-tricks.com/performant-expandable-animations-building-keyframes-on-the-fly/ https://css-tricks.com/performant-expandable-animations-building-keyframes-on-the-fly/#comments Wed, 01 Apr 2020 14:46:16 +0000 https://css-tricks.com/?p=304747 Animations have come a long way, continuously providing developers with better tools. CSS Animations, in particular, have defined the ground floor to solve the majority of uses cases. However, there are some animations that require a little bit more …


Performant Expandable Animations: Building Keyframes on the Fly originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Animations have come a long way, continuously providing developers with better tools. CSS Animations, in particular, have defined the ground floor to solve the majority of uses cases. However, there are some animations that require a little bit more work.

You probably know that animations should run on the composite layer. (I won’t extend myself here, but if you want to know more, check this article.) That means animating transform or opacity properties that don’t trigger layout or paint layers. Animating properties like height and width is a big no-no, as they trigger those layers, which force the browser to recalculate styles.

On top of that, even when animating transform properties, if you want to truly hit 60 FPS animations, you probably should get a little help from JavaScript, using the FLIP technique for extra smoother animations! 

However, the problem of using transform for expandable animations is that the scale function isn’t exactly the same as animating width/height properties. It creates a skewed effect on the content, as all elements get stretched (when scaling up) or squeezed (when scaling down).

So, because of that, my go-to solution has been (and probably still is, for reasons I will detail later), technique #3 from Brandon Smith’s article. This still has a transition on height, but uses Javascript to calculate the content size, and force a transition using requestAnimationFrame. At OutSystems, we actually used this to build the animation for the OutSystems UI Accordion Pattern.

Generating keyframes with JavaScript

Recently, I stumbled on another great article from Paul Lewis, that details a new solution for expanding and collapsing animations, which motivated me to write this article and spread this technique around.

Using his words, the main idea consists of generating dynamic keyframes, stepping…

[…] from 0 to 100 and calculate what scale values would be needed for the element and its contents. These can then be boiled down to a string, which can be injected into the page as a style element. 

To achieve this, there are three main steps.

Step 1: Calculate the start and end states

We need to calculate the correct scale value for both states. That means we use getBoundingClientRect() on the element that will serve as a proxy for the start state, and divide it with the value from the end state. It should be something like this:

function calculateStartScale () {
  const start= startElement.getBoundingClientRect();
  const end= endElement.getBoundingClientRect();
  return {
    x: start.width / end.width,
    y: start.height / end.height
  };
}

Step 2: Generate the Keyframes

Now, we need to run a for loop, using the number of frames needed as the length. (It shouldn’t really be less than 60 to ensure a smooth animation.) Then, in each iteration, we calculate the correct easing value, using an ease function:

function ease (v, pow=4) {
  return 1 - Math.pow(1 - v, pow);
}

let easedStep = ease(i / frame);

With that value, we’ll get the scale of the element on the current step, using the following math:

const xScale = x + (1 - x) * easedStep;
const yScale = y + (1 - y) * easedStep;

And then we add the step to the animation string:

animation += `${step}% {
  transform: scale(${xScale}, ${yScale});
}`;

To avoid the content to get stretched/ skewed, we should perform a counter animation on it, using the inverted values:

const invXScale = 1 / xScale;
const invYScale = 1 / yScale;

inverseAnimation += `${step}% {
  transform: scale(${invXScale}, ${invYScale});
}`;

Finally, we can return the completed animations, or directly inject them in a newly created style tag.

Step 3: Enable the CSS animations

On the CSS side of things, we need to enable the animations on the correct elements:

.element--expanded {
  animation-name: animation;
  animation-duration: 300ms;
  animation-timing-function: step-end;
}

.element-contents--expanded {
  animation-name: inverseAnimation ;
  animation-duration: 300ms;
  animation-timing-function: step-end;
}

You can check the example of a Menu from Paul Lewis article, on Codepen (courtesy of Chris):

Building an expandable section

After grasping these baseline concepts, I wanted to check if I could apply this technique to a different use case, like a expandable section.

We only need to animate the height in this case, specifically on the function to calculate scales. We’re getting the Y value from the section title, to serve as the collapsed state, and the whole section to represent the expanded state:

    _calculateScales () {
      var collapsed = this._sectionItemTitle.getBoundingClientRect();
      var expanded = this._section.getBoundingClientRect();
      
      // create css variable with collapsed height, to apply on the wrapper
      this._sectionWrapper.style.setProperty('--title-height', collapsed.height + 'px');

      this._collapsed = {
        y: collapsed.height / expanded.height
      }
    }

Since we want the expanded section to have absolute positioning (in order to avoid it taking space when in a collapsed state), we are setting the CSS variable for it with the collapsed height, applied on the wrapper. That will be the only element with relative positioning.

Next comes the function to create the keyframes: _createEaseAnimations(). This doesn’t differ much from what was explained above. For this use case, we actually need to create four animations:

  1. The animation to expand the wrapper
  2. The counter-expand animation on the content
  3. The animation to collapse the wrapper
  4. The counter-collapse animation on the content

We follow the same approach as before, running a for loop with a length of 60 (to get a smooth 60 FPS animation), and create a keyframe percentage, based on the eased step. Then, we push it to the final animations strings:

outerAnimation.push(`
  ${percentage}% {
    transform: scaleY(${yScale});
  }`);
  
innerAnimation.push(`
  ${percentage}% {
    transform: scaleY(${invScaleY});
  }`);

We start by creating a style tag to hold the finished animations. As this is built as a constructor, to be able to easily add multiple patterns, we want to have all these generated animations on the same stylesheet. So, first, we validate if the element exists. If not, we create it and add a meaningful class name. Otherwise, you would end up with a stylesheet for each section expandable, which is not ideal.

 var sectionEase = document.querySelector('.section-animations');
 if (!sectionEase) {
  sectionEase = document.createElement('style');
  sectionEase.classList.add('section-animations');
 }

Speaking of that, you may already be wondering, “Hmm, if we have multiple expandable sections, wouldn’t they still be using the same-named animation, with possibly wrong values for their content?” 

You’re absolutely right! So, to prevent that, we are also generating dynamic animation names. Cool, right?

We make use of the index passed to the constructor from the for loop when making the querySelectorAll('.section') to add a unique element to the name:

var sectionExpandAnimationName = "sectionExpandAnimation" + index;
var sectionExpandContentsAnimationName = "sectionExpandContentsAnimation" + index;

Then we use this name to set a CSS variable on the current expandable section. As this variable is only in this scope, we just need to set the animation to the new variable in the CSS, and each pattern will get its respective animation-name value.

.section.is--expanded {
  animation-name: var(--sectionExpandAnimation);
}

.is--expanded .section-item {
  animation-name: var(--sectionExpandContentsAnimation);
}

.section.is--collapsed {
  animation-name: var(--sectionCollapseAnimation);
}

.is--collapsed .section-item {
  animation-name: var(--sectionCollapseContentsAnimation);
}

The rest of the script is related to adding event listeners, functions to toggle the collapse/expand status and some accessibility improvements.

About the HTML and CSS: it needs a little bit of extra work to make the expandable functionality work. We need an extra wrapper to be the relative element that doesn’t animate. The expandable children have an absolute position so that they don’t occupy space when collapsed.

Remember, since we need to make counter animations, we make it scale full size in order to avoid a skew effect on the content.

.section-item-wrapper {
  min-height: var(--title-height);
  position: relative;
}

.section {
  animation-duration: 300ms;
  animation-timing-function: step-end;
  contain: content;
  left: 0;
  position: absolute;
  top: 0;
  transform-origin: top left;
  will-change: transform;
}

.section-item {
  animation-duration: 300ms;
  animation-timing-function: step-end;
  contain: content;
  transform-origin: top left;
  will-change: transform;  
}

I would like to highlight the importance of the animation-timing-functionproperty. It should be set to linear or step-end to avoid easing between each keyframe.

The will-change property — as you probably know — will enable GPU acceleration for the transform animation for an even smoother experience. And using the contains property, with a value of contents, will help the browser treat the element independently from the rest of the DOM tree, limiting the area before it recalculates the layout, style, paint and size properties.

We use visibility and opacity to hide the content, and stop screen readers to access it, when collapsed.

.section-item-content {
  opacity: 1;
  transition: opacity 500ms ease;
}

.is--collapsed .section-item-content {
  opacity: 0;
  visibility: hidden;
}

And finally, we have our section expandable! Here’s the complete code and demo for you to check:

Performance check

Anytime we work with animations, performance ought to be in the back of our mind. So, let’s use developer tools to check if all this work was worthy, performance-wise. Using the Performance tab (I’m using Chrome DevTools), we can analyze the FPS and the CPU usage, during the animations.

And the results are great!

The higher the green bar, the higher the frames. And there’s no junk either, which would be signed by red sections.

Using the FPS meter tool to check the values at greater detail, we can see that it constantly hits the 60 FPS mark, even with abusive usage.

Final considerations

So, what’s the verdict? Does this replace all other methods? Is this the “Holy Grail” solution?

In my opinion, no. 

But… that’s OK, really! It’s another solution on the list. And, as is true with any other method, it should be analyzed if it’s the best approach for the use-case.

This technique definitely has its merits. As Paul Lewis says, this does take a lot of work to prepare. But, on the flip side, we only need to do it once, when the page loads. During interactions, we are merely toggling classes (and attributes in some cases, for accessibility).

However, this brings some limitations to the UI of the elements. As you could see for the expandable section element, the counter-scale makes it much more reliable for absolute and off-canvas elements, like floating-actions or menus. It’s also difficult to styled borders because it’s using overflow: hidden.

Nevertheless, I think there is tons of potential with this approach. Let me know what you think!


Performant Expandable Animations: Building Keyframes on the Fly originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/performant-expandable-animations-building-keyframes-on-the-fly/feed/ 3 304747
Animated Matryoshka Dolls in CSS https://css-tricks.com/animated-matryoshka-dolls-in-css/ https://css-tricks.com/animated-matryoshka-dolls-in-css/#comments Thu, 27 Feb 2020 15:23:10 +0000 https://css-tricks.com/?p=303748 Here’s a fun one. How might we create a set of those cool Matryoshka dolls where they nest inside one another… but in CSS?

I toyed with this idea in my head for a little while. Then, I saw a …


Animated Matryoshka Dolls in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Here’s a fun one. How might we create a set of those cool Matryoshka dolls where they nest inside one another… but in CSS?

I toyed with this idea in my head for a little while. Then, I saw a tweet from CSS-Tricks and the article image had the dolls. I took that as a sign! It was time to put fingers to the keyboard.

Our goal here is to make these fun and interactive, where we can click on a doll to open it up and reveal another, smaller doll. Oh, and stick with just CSS for the functionality. And while we’re at it, let’s replace the dolls with our own character, say a CodePen bear. Something like this:

We won’t dwell on making things pretty to start. Let’s get some markup on the page and thrash out the mechanics first.

We can’t have an infinite amount of dolls. When we reach the innermost doll, it’d be nice to be able to reset the dolls without having to do a page refresh. A neat trick for this is to wrap our scene in an HTML form. That way we can use an input and set the type attribute to reset to avoid using any JavaScript.

<form>
  <input type="reset" id="reset"/>
  <label for="reset" title="Reset">Reset</label>
</form>

Next, we need some dolls. Or bears. Or something to start with. The key here will be to use the classic checkbox hack and any associated form labels. As a note, I’m going to use Pug to handle the markup because it supports loops, making things a little easier. But, you can certainly write the HTML by hand. Here’s the start with form fields and labels that set up the checkbox hack.

Try clicking some of the inputs and hitting the Reset input. They all become unchecked. Nice, we’ll use that.

We have some interactivity but nothing is really happening yet. Here’s the plan:

  1. We’ll only show one checkbox at a time
  2. Checking a checkbox should reveal the label for the next checkbox.
  3. When we get to the last checkbox, there our only option should be to reset the form.

The trick will be to make use of the CSS adjacent sibling combinator (+).

input:checked + label + input + label {
  display: block;
}

When a checkbox is checked, we need to show the label for the next doll, which will be three siblings along in the DOM. How do we make the first label visible? Give it an explicit display: block via inline styles in our markup. Putting this together, we have something along these lines:

Clicking each label reveals the next. Hold on, the last label isn’t shown! That’s correct. And that’s because the last label doesn’t have a checkbox. We need to add a rule that caters to that last label.

input:checked + label + input + label,
input:checked + label + label {
  display: block;
}

Cool. We’re getting somewhere. That’s the basic mechanics. Now things are going to get a little trickier. 

Basic styling

So, you might be thinking, “Why aren’t we hiding the checked label?” Good question! But, if we hide it straight away, we won’t have any transition between the current doll and the next. Before we start animating our dolls, let’s create basic boxes that will represent a doll. We can style them up so they mimic the doll outline without the detail.

.doll {
  color: #fff;
  cursor: pointer;
  height: 200px;
  font-size: 2rem;
  left: 50%;
  position: absolute;
  text-align: center;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 100px;
}

.doll:nth-of-type(even) {
  background: #00f;
}

.doll:nth-of-type(odd) {
  background: #f00;
}

Clicking one doll instantly reveals the next one and, when we’ve reached the last doll, we can reset the form to start again. That’s what we want here.

The mechanics

We are going to animate the dolls based on a center point. Our animation will consist of many steps:

  1. Slide the current doll out to the left.
  2. Open the doll to reveal the next one.
  3. Move the next doll where the current one started.
  4. Make the current doll fade out.
  5. Assign the next doll as the current doll.

Let’s start by sliding the current doll out to the left. We apply an animation when we click a label. Using the :checked pseudo-selector we can target the current doll. At this point, it’s worth noting that we are going to use CSS variables to control animation speed and behavior. This will make it easier to chain animations on the labels.

:root {
  --speed: 0.25;
  --base-slide: 100;
  --slide-distance: 60;
}

input:checked + label {
  animation: slideLeft calc(var(--speed) * 1s) forwards;
}

@keyframes slideLeft {
  to {
    transform: translate(calc((var(--base-slide) * -1px) + var(--slide-distance) * -1%), 0);
  }
}

That looks great. But there’s an issue. As soon as we click a label, we could click it again and reset the animation. We don’t want that to happen.

How can we get around this? We can remove pointer events from a label once it’s been clicked.

input:checked + label {
  animation: slideLeft calc(var(--speed) * 1s) forwards;
  pointer-events: none;
}

Great! Now once we have started, we can’t stop the animation chain from happening.

Next up, we need to crack open the doll to reveal the next one. This is where things get tricky because we are going to need some extra elements, not only to create the effect that the doll is opening up, but also to reveal the next doll inside of it. That’s right: we need to duplicate the inner doll. The trick here is to reveal a “fake” doll that we swap for the real one once we’ve animated it. This also means delaying the reveal of the next label.

Now our markup updates labels so that they contains span elements.

<label class="doll" for="doll--1">
  <span class="doll doll--dummy"></span>
  <span class="doll__half doll__half--top">Top</span>
  <span class="doll__half doll__half--bottom">Bottom</span>
</label>

These will act as the “dummy” doll as well as the lid and base for the current doll.

.doll {
  color: #fff;
  cursor: pointer;
  height: 200px;
  font-size: 2rem;
  position: absolute;
  text-align: center;
  width: 100px;
}

.doll:nth-of-type(even) {
  --bg: #00f;
  --dummy-bg: #f00;
}

.doll:nth-of-type(odd) {
  --bg: #f00;
  --dummy-bg: #00f;
}

.doll__half {
  background: var(--bg);
  position: absolute;
  width: 100%;
  height: 50%;
  left: 0;
}

.doll__half--top {
  top: 0;
}

.doll__half--bottom {
  bottom: 0;
}

.doll__dummy {
  background: var(--dummy-bg);
  height: 100%;
  width: 100%;
  position: absolute;
  top: 0;
  left: 0;
}

The lid requires three translations to create the opening effect: one to pop it up, one to move it left and then one to pop it down.

@keyframes open {
  0% {
    transform: translate(0, 0);
  }
  33.333333333333336% {
    transform: translate(0, -100%);
  }
  66.66666666666667% {
    transform: translate(-100%, -100%);
  }
  100% {
    transform: translate(-100%, 100%);
  }
}

Next is where we can use CSS custom properties to handle changing values. Once the doll has slid over to the left, we can open it. But how do we know how long to delay it from opening until that happens? We can use the --speed custom property we defined earlier to calculate the correct delay.

It looks a little quick if we use the --speed value as it is, so let’s multiply it by two seconds:

input:checked + .doll {
  animation: slideLeft calc(var(--speed) * 1s) forwards;
  pointer-events: none;
}

input:checked + .doll .doll__half--top {
  animation: open calc(var(--speed) * 2s) calc(var(--speed) * 1s) forwards; // highlight
}

Much better:

Now we need to move the inner “dummy” doll to the new position. This animation is like the open animation in that it consists of three stages. Again, that’s one to move up, one to move right, and one to set down. It’s like the slide animation, too. We are going to use CSS custom properties to determine the distance that the doll moves.

:root {
  // Introduce a new variable that defines how high the dummy doll should pop out.
  --pop-height: 60;
}

@keyframes move {
  0% {
    transform: translate(0, 0) translate(0, 0);
  }
  33.333333333333336% {
    transform: translate(0, calc(var(--pop-height) * -1%)) translate(0, 0);
  }
  66.66666666666667% {
    transform: translate(0, calc(var(--pop-height) * -1%)) translate(calc((var(--base-slide) * 1px) + var(--slide-distance) * 1%), 0);
  }
  100% {
    transform: translate(0, calc(var(--pop-height) * -1%)) translate(calc((var(--base-slide) * 1px) + var(--slide-distance) * 1%), calc(var(--pop-height) * 1%));
  }
}

Almost there! 

The only thing is that the next doll is available as soon as we click a doll. that means we can spam click our way through the set.

Technically, the next doll shouldn’t show until the “fake” one has moved into place. It’s only once the “fake” doll is in place that we can hide it and reveal the real one. That means we going to use zero-second scale animations! That’s right. We can play pretend by delaying two zero-second animations and using animation-fill-mode.

@keyframes appear {
  from {
    transform: scale(0);
  }
}

We actually only need one set of @keyframes. because we can re-use what we have to create the opposite movement with animation-direction: reverse. With that in mind, all our animations get applied something like this:

// The next doll
input:checked + .doll + input + .doll,
// The last doll (doesn't have an input)
input:checked + .doll + .doll {
  animation: appear 0s calc(var(--speed) * 5s) both;
  display: block;
}

// The current doll
input:checked + .doll,
// The current doll that isn't the first. Specificity prevails
input:checked + .doll + input:checked + .doll {
  animation: slideLeft calc(var(--speed) * 1s) forwards;
  pointer-events: none;
}

input:checked + .doll .doll__half--top,
input:checked + .doll + input:checked + .doll .doll__half--top {
  animation: open calc(var(--speed) * 2s) calc(var(--speed) * 1s) forwards;
}

input:checked + .doll .doll__dummy,
input:checked + .doll + input:checked + .doll .doll__dummy {
  animation: move calc(var(--speed) * 2s) calc(var(--speed) * 3s) forwards, appear 0s calc(var(--speed) * 5s) reverse forwards;
}

Note how important the variables are, especially where we are chaining animations. That gets us almost where we need to be.

I can hear it now: “They’re all the same size!” Yep. That’s the missing piece. They need to scale down. The trick here is to adjust the markup again and make use of CSS custom properties yet again.

<input id="doll--0" type="checkbox"/>
<label class="doll" for="doll--0" style="display: block; --doll-index: 0;">
  <span class="doll__dummy-container">
    <span class="doll__dummy"></span>
  </span> //highlight
  <span class="doll__container">
    <span class="doll__half doll__half--top"></span>
    <span class="doll__half doll__half--bottom"></span>
  </span>
</label>

We just introduced a CSS custom property inline that tells us the index of the doll. We can use this to generate a scale of each half as well as the fake inner doll. The halves will have to scale to match the actual doll size, but the fake inner doll scale will need to match that of the next doll. Tricky!

We can apply these scales inside the containers so that our animations are not affected.

:root {
  --scale-step: 0.05;
}

.doll__container,
.doll__dummy-container {
  height: 100%;
  left: 0;
  position: absolute;
  top: 0;
  width: 100%;
}

.doll__container {
  transform: scale(calc(1 - ((var(--doll-index)) * var(--scale-step))));
  transform-origin: bottom;
}

.doll__dummy {
  height: 100%;
  left: 0;
  position: absolute;
  top: 0;
  transform: scale(calc(1 - ((var(--doll-index) + 1) * var(--scale-step))));
  transform-origin: bottom center;
  width: 100%;
}

Note how the .doll__dummy class uses var(--doll-index) + 1) to calculate the scale so that it matches the next doll.  👍

Lastly, we re-assign the animation to the .doll__dummy-container class instead of the .doll__dummy class.

input:checked + .doll .doll__dummy-container,
input:checked + .doll + input:checked + .doll .doll__dummy-container {
  animation: move calc(var(--speed) * 2s) calc(var(--speed) * 3s) forwards, appear 0s calc(var(--speed) * 5s) reverse forwards;
}

Here’s a demo where the containers have been given a background color to see what’s happening.

We can see that, although the content size changes, they remain the same size. This makes for consistent animation behavior and makes the code easier to maintain.

Finishing touches

Wow, things are looking pretty slick! All we need are some finishing touches and we are done!

The scene starts to look cluttered because we’re stacking the “old” dolls off to the side when a new one is introduced. So let’s slide a doll out of view when the next one is revealed to clean that mess up.

@keyframes slideOut {
  from {
    transform: translate(calc((var(--base-slide) * -1px) + var(--slide-distance) * -1%), 0);
  }
  to {
    opacity: 0;
    transform: translate(calc((var(--base-slide) * -1px) + var(--slide-distance) * -2%), 0);
  }
}

input:checked + .doll,
input:checked + .doll + input:checked + .doll {
  animation: slideLeft calc(var(--speed) * 1s) forwards,
    slideOut calc(var(--speed) * 1s) calc(var(--speed) * 6s) forwards;
  pointer-events: none;
}

The new slideOut animation fades the doll out while it translates to the left. Perfect.  👍

That’s it for the CSS trickery we need to make this effect work. All that’s left style the dolls and the scene.

We have many options to style the dolls. We could use a background image, CSS illustration, SVG, or what have you. We could even throw together some emoji dolls that use random inline hues!

Let’s go with inline SVG.

I’m basically using the same underlying mechanics we’ve already covered. The difference is that I’m also generating inline variables for hue and lightness so the bears sport different shirt colors.


There we have it! Stacking dolls — err, bears — with nothing but HTML and CSS! All the code for all the steps is available in this CodePen collection. Questions or suggestions? Feel free to reach out to me here in the comments.


Animated Matryoshka Dolls in CSS originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/animated-matryoshka-dolls-in-css/feed/ 4 303748
The State of Changing Gradients with CSS Transitions and Animations https://css-tricks.com/the-state-of-changing-gradients-with-css-transitions-and-animations/ https://css-tricks.com/the-state-of-changing-gradients-with-css-transitions-and-animations/#comments Fri, 01 Jun 2018 14:30:40 +0000 http://css-tricks.com/?p=271227 Back in 2012, Internet Explorer 10 came out and, among other things, it finally supported CSS gradients and, in addition to that, the ability to animate them with just CSS! No other browser supported this at the time, but I …


The State of Changing Gradients with CSS Transitions and Animations originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Back in 2012, Internet Explorer 10 came out and, among other things, it finally supported CSS gradients and, in addition to that, the ability to animate them with just CSS! No other browser supported this at the time, but I was hopeful for the future.

Sadly, six years have passed and nothing has changed in this department. Edge supports animating gradients with CSS, just like IE 10 did back then, but no other browser has added support for this. And while animating background-size or background-position or the opacity or rotation of a pseudo element layered on top can take us a long way in terms of achieving cool effects, these workarounds are still limited.

There are effects we cannot reproduce without adding lots of extra elements or lots of extra gradients, such as “the blinds effect” seen below.

Animated GIF showing a recording of the opening and closing blinds effect. When the blinds are closed, we only see a grey background, when the blinds start to open, we start seeing vertical orange strips (the light coming in) that grow horizontally until the blinds are fully open, so we only see an orange background. After that, the blinds start to close, so the vertical orange strips start getting narrower until they're reduced to nothing when the blinds are fully closed and we only see a grey background again. The whole cycle then repeats itself.
The blinds effect (live demo, Edge/ IE 10+ only).

In Edge, getting the above effect is achieved with a keyframe animation:

html {
  background: linear-gradient(90deg, #f90 0%, #444 0) 50%/ 5em;
  animation: blinds 1s ease-in-out infinite alternate;
}

@keyframes blinds {
  to {
    background-image: linear-gradient(90deg, #f90 100%, #444 0);
  }
}

If that seems WET, we can DRY it up with a touch of Sass:

@function blinds($open: 0) {
  @return linear-gradient(90deg, #f90 $open*100%, #444 0);
}

html {
  background: blinds() 50%/ 5em;
  animation: blinds 1s ease-in-out infinite alternate;
}

@keyframes blinds { to { background-image: blinds(1) } }

While we’ve made the code we write and what we’ll need to edit later a lot more maintainable, we still have repetition in the compiled CSS and we’re limited by the fact that we can only animate between stops with the same unit — while animating from 0% to 100% works just fine, trying to use 0 or 0px instead of 0% results in no animation happening anymore. Not to mention that Chrome and Firefox just flip from orange to grey with no stop position animation at all!

Fortunately, these days we have an even better option: CSS variables!

Right out of the box, CSS variables are not animatable, though we can get transition (but not animation!) effects if the property we use them for is animatable. For example, when used inside a transform function, we can transition the transform the property.

Let’s consider the example of a box that gets shifted and squished when a checkbox is checked. On this box, we set a transform that depends on a factor --f which is initially 1:

.box {
  /* basic styles like dimensions and background */
  --f: 1;
  transform: translate(calc((1 - var(--f))*100vw)) scalex(var(--f));
}

When the checkbox is :checked, we change the value of the CSS variable --f to .5:

:checked ~ .box { --f: .5 }

Setting a transition on the .box makes it go smoothly from one state to the other:

.box {
  /* same styles as before */
  transition: transform .3s ease-in;
}

Note that this doesn’t really work in the current version of Edge due to this bug.

However, CSS gradients are background images, which are only animatable in Edge and IE 10+. So, while we can make things easier for ourselves and reduce the amount of generated CSS for transitions (as seen in the code below), we’re still not making progress in terms of extending support.

.blinds {
  background: linear-gradient(90deg, #f90 var(--pos, 0%), #444 0) 50%/ 5em;
  transition: .3s ease-in-out;
    
  :checked ~ & { --pos: 100%; }
}
Animated gif. The blinds opening effect happens on checking an 'open blinds' checkbox, while unchecking it triggers the closing effect.
Open/close blinds on checking/unchecking the checkbox (live demo, Edge only).

Enter Houdini, which allows us to register custom properties and then animate them. Currently, this is only supported by Blink browsers behind the Experimental Web Platform features flag, but it’s still extending support a bit from Edge alone.

Screenshot showing the Experimental Web Platform features flag being enabled in Chrome.
The Experimental Web Platform features flag enabled in Chrome.

Going back to our example, we register the --pos custom property:

CSS.registerProperty({
  name: '--pos', 
  syntax: '<length-percentage>', 
  initialValue: '0%', 
  inherits: true
});

Note that means it accepts not only length and percentage values, but also calc() combinations of them. By contrast, | only accepts length and percentage values, but not calc() combinations of them.

Note that explicitly specifying inherits is now mandatory, even though it was optional in previous versions of the spec.

However, doing this doesn’t make any difference in Chrome, even with the flag enabled, probably because, in the case of transitions, what’s being transitioned is the property whose value depends on the CSS variable and not the CSS variable itself. And since we generally can’t transition between two background images in Chrome in general, this fails as well.

It does work in Edge, but it worked in Edge even without registering the --pos variable because Edge allows us to transition between gradients in general.

What does work in Blink browsers with the flag enabled is having an animation instead of a transition.

html {
  background: linear-gradient(90deg, #f90 var(--pos, 0%), #444 0) 50%/ 5em;
  animation: blinds .85s ease-in-out infinite alternate;
}

@keyframes blinds { to { --pos: 100%; } }

However, this is now not working in Edge anymore because, while Edge can animate between gradient backgrounds, it cannot do the same for custom properties.

So we need to take an alternative approach for Edge here. This is where @supports comes in handy, since all we have to do is check whether a -ms- prefixed property is supported.

@function grad($pos: 100%) {
  @return linear-gradient(90deg, #f90 $pos, #444 0);
}

html {
  /* same as before */
    
  @supports (-ms-user-select: none) {
    background-image: grad(0%);
    animation-name: blinds-alt;
  }
}

@keyframes blinds-alt { to { background-image: grad() } }

Stop positions aren’t the only thing we can animate this way. We can do the same thing for the gradient angle. The idea behind it is pretty much the same, except now our animation isn’t an alternating one anymore and we use an easeInOutBack kind of timing function.

@function grad($ang: 1turn) {
  @return linear-gradient($ang, #f90 50%, #444 0);
}

html {
  background: grad(var(--ang, 0deg));
  animation: rot 2s cubic-bezier(.68, -.57, .26, 1.65) infinite;
  
  @supports (-ms-user-select: none) {
    background-image: grad(0turn);
    animation-name: rot-alt;
  }
}

@keyframes rot { to { --ang: 1turn; } }

@keyframes rot-alt { to { background-image: grad(); } }

Remember that, just like in the case of stop positions, we can only animate between gradient angles expressed in the same unit in Edge, so calling our Sass function with grad(0deg) instead of grad(0turn) doesn’t work.

And, of course, the CSS variable we now use accepts angle values instead of lengths and percentages:

CSS.registerProperty({
  name: '--ang', 
  syntax: '<angle>', 
  initialValue: '0deg', 
  inherits: true
});
Animated gif. Shows a top to bottom gradient with an abrupt change from grey to orange at 50%. The angle of this gradient is animated using a easeInOutBack timing function (which overshoots the end values at both ends).
Sweeping around (live demo, Blink browsers with flag and Edge only).

In a similar fashion, we can also animate radial gradients. And the really cool thing about the CSS variable approach is that it allows us to animate different components of the gradient differently, which is something that’s not possible when animating gradients as a whole the way Edge does (which is why the following demos don’t work as well in Edge).

Let’s say we have the following radial-gradient():

$p: 9%;

html {
  --x: #{$p};
  --y: #{$p};
  background: radial-gradient(circle at var(--x) var(--y), #f90, #444 $p);
}

We register the --x and --y variables:

CSS.registerProperty({
  name: '--x', 
  syntax: '<length-percentage>', 
  initialValue: '0%', 
  inherits: true
});

CSS.registerProperty({
  name: '--y', 
  syntax: '<length-percentage>', 
  initialValue: '0%', 
  inherits: true
});

Then we add the animations:

html {
  /* same as before */
  animation: a 0s ease-in-out -2.3s alternate infinite;
  animation-name: x, y;
  animation-duration: 4.1s, 2.9s;
}

@keyframes x { to { --x: #{100% - $p} } }
@keyframes y { to { --y: #{100% - $p} } }

The result we get can be seen below:

Animated GIF. Shows a moving glowing orange light on a grey background. This is achieved by animating the coordinates of the central point of a radial gradient independently with the help of CSS variables and Houdini.
Moving light (live demo, Blink browsers with flag only).

We can use this technique of animating the different custom properties we use inside the gradient function to make the blinds in our initial example close the other way instead of going back. In order to do this, we introduce two more CSS variables, --c0 and --c1:

$c: #f90 #444;

html {
  --c0: #{nth($c, 1)};
  --c1: #{nth($c, 2)};
  background: linear-gradient(90deg, var(--c0) var(--pos, 0%), var(--c1) 0) 50%/ 5em;
}

We register all these custom properties:

CSS.registerProperty({
  name: '--pos', 
  syntax: '<length-percentage>', 
  initialValue: '0%', 
  inherits: true
});

CSS.registerProperty({
  name: '--c0', 
  syntax: '<color>', 
  initialValue: 'red', 
  inherits: true
});

/* same for --c1 */

We use the same animation as before for the position of the first stop --pos and, in addition to this, we introduce two steps() animations for the other two variables, switching their values every time an iteration of the first animation (the one changing the value of --pos) is completed:

$t: 1s;

html {
  /* same as before */
  animation: a 0s infinite;
  animation-name: c0, pos, c1;
  animation-duration: 2*$t, $t;
  animation-timing-function: steps(1), ease-in-out;
}

@keyframes pos { to { --pos: 100%; } }

@keyframes c0 { 50% { --c0: #{nth($c, 2)} } }
@keyframes c1 { 50% { --c1: #{nth($c, 1)} } }

And we get the following result:

Animated GIF. Shows the blinds effect with the blinds closing the other way. Once the vertical orange strips (openings) have expanded horizontally such that they cover the whole background, they don't start contracting again. Instead, vertical grey orange strips start expanding from nothing until they cover the whole background.
Another version of the blinds animation (live demo, Blink browsers with flag only).

We can also apply this to a radial-gradient() (nothing but the background declaration changes):

background: radial-gradient(circle, var(--c0) var(--pos, 0%), var(--c1) 0);
Animated gif. We start with a grey background and we have an orange disc growing from nothing in the middle until it covers everything. Then we have a grey disc growing from nothing in the middle until it covers the entire background and we're back where we started from: a grey background.
Growing discs (live demo, Blink browsers with flag only).

The exact same tactic works for conic-gradient() as well:

background: conic-gradient(var(--c0) var(--pos, 0%), var(--c1) 0);
Animated gif. We start with a grey background and we have an orange pie slice (circular sector) growing from nothing to covering everything around the central point. Then we have a grey pie slice growing from nothing to covering everything around the central point and we're back where we started from: a grey background.
Growing slices (live demo, Blink browsers with flag only).

Repeating gradients are also an option creating a ripple-like effect in the radial case:

$p: 2em;

html {
  /* same as before */
  background: repeating-radial-gradient(circle, 
    var(--c0) 0 var(--pos, 0px), var(--c1) 0 $p);
}

@keyframes pos { 90%, 100% { --pos: #{$p} } }
Animated gif. We start with a grey background and we have concentric orange circles growing outwards from really thin until they meet and cover everything, so now it looks like we have an orange background. Then we have grey circles growing outwards from really thin until they cover the entire background and we're back where we started from: a grey background.
Ripples (live demo, Blink browsers with flag only).

And a helix/rays effect in the conic case:

$p: 5%;

html {
  /* same as before */
  background: repeating-conic-gradient(
    var(--c0) 0 var(--pos, 0%), var(--c1) 0 $p);
}

@keyframes pos { 90%, 100% { --pos: #{$p} } }
Animated gif. We start with a grey background and we have orange rays growing clockwise from really thin until they meet and cover everything, so now it looks like we have an orange background. Then we have grey rays growing clockwise from really thin until they cover the entire background and we're back where we started from: a grey background.
Growing rays (live demo, Blink browsers with flag only).

We can also add another CSS variable to make things more interesting:

$n: 20;

html {
  /* same as before */
  background: radial-gradient(circle at var(--o, 50% 50%), 
    var(--c0) var(--pos, 0%), var(--c1) 0);
  animation: a 0s infinite;
  animation-name: c0, o, pos, c1;
  animation-duration: 2*$t, $n*$t, $t;
  animation-timing-function: steps(1), steps(1), ease-in-out;
}

@keyframes o {
  @for $i from 0 to $n {
    #{$i*100%/$n} { --o: #{random(100)*1%} #{random(100)*1%} }
  }
}

We need to register this variable for the whole thing to work:

CSS.registerProperty({
  name: '--o', 
  syntax: '<length-percentage>', 
  initialValue: '50%', 
  inherits: true
});

And that’s it! The result can be seen below:

Animated gif. We start with a grey background and we have an oranges disc, randomly positioned, growing from nothing until it covers everything, so now it looks like we have an orange background. Then we have grey disc, randomly positioned, growing from nothing until it covers the entire background and we're back where we started from: a grey background.
Randomly positioned growing discs (live demo, Blink browsers with flag only).

I’d say the future of changing gradients with keyframe animations looks pretty cool. But in the meanwhile, for cross-browser solutions, the JavaScript way remains the only valid one.


The State of Changing Gradients with CSS Transitions and Animations originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/the-state-of-changing-gradients-with-css-transitions-and-animations/feed/ 2 271227
CSS Animations vs Web Animations API https://css-tricks.com/css-animations-vs-web-animations-api/ https://css-tricks.com/css-animations-vs-web-animations-api/#comments Tue, 13 Jun 2017 11:57:15 +0000 http://css-tricks.com/?p=255477 There is a native API for animation in JavaScript known as the Web Animations API. We’ll call it WAAPI in this post. MDN has good documentation on it, and Dan Wilson has a great article series.

In this article, …


CSS Animations vs Web Animations API originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
There is a native API for animation in JavaScript known as the Web Animations API. We’ll call it WAAPI in this post. MDN has good documentation on it, and Dan Wilson has a great article series.

In this article, we’ll compare WAAPI and animations done in CSS.

A note on browser support

WAAPI has a comprehensive and robust polyfill, making it usable in production today, even while browser support is limited.

As ever, you can check Can I Use for browser support data. However, that doesn’t provide very good info on support of all the sub features of WAAPI. Here’s a checker for that:

See the Pen WAAPI Browser Support Test by Dan Wilson (@danwilson) on CodePen.

To experiment with all features without a polyfill, use Firefox Nightly.

The basics of WAAPI

If you’ve ever used jQuery’s .animate(), the basic syntax of WAAPI should look pretty familiar. 

var element = document.querySelector('.animate-me');
element.animate(keyframes, 1000);

The animate method accepts two parameters: keyframes and duration. In contrast to jQuery, not only does it have the benefit of being built into the browser, it’s also more performant.

The first argument, the keyframes, should be an array of objects. Each object is a keyframe in our animation. Here’s a simple example:

var keyframes = [
  { opacity: 0 },
  { opacity: 1 }
];

The second argument, the duration, is how long we want the animation to last . In the example above it is 1000 milliseconds. Let’s look at a more exciting example.

Recreating an animista CSS animation with WAAPI

Here’s some CSS code I yanked from the awesome animista for something calling itself the “slide-in-blurred-top” entrance animation. It looks pretty sweet. 

The actual perf is much better than this GIF.

Here’s those keyframes in CSS:

0% {
  transform: translateY(-1000px) scaleY(2.5) scaleX(.2);
  transform-origin: 50% 0;
  filter: blur(40px);
  opacity: 0;
}
100% {
  transform: translateY(0) scaleY(1) scaleX(1);
  transform-origin: 50% 50%;
  filter: blur(0);
  opacity: 1;
}

Here’s the same code in WAAPI:

var keyframes = [
  { 
    transform: 'translateY(-1000px) scaleY(2.5) scaleX(.2)', 
    transformOrigin: '50% 0', filter: 'blur(40px)', opacity: 0 
  },
  { 
    transform: 'translateY(0) scaleY(1) scaleX(1)',
    transformOrigin: '50% 50%',
    filter: 'blur(0)',
    opacity: 1 
  }
];

We’ve already seen how easy it is to apply the keyframes to whichever element we want to animate:

element.animate(keyframes, 700);

To keep the example simple, I’ve only specified the duration. However, we can use this second parameter to pass in far more options. At the very least, we should also specify an easing. Here’s the full list of available options with some example values:

var options = {
  iterations: Infinity,
  iterationStart: 0,
  delay: 0,
  endDelay: 0,
  direction: 'alternate',
  duration: 700,
  fill: 'forwards',
  easing: 'ease-out',
}
element.animate(keyframes, options);

With these options, our animation will start at the beginning with no delay and loop forever alternating between playing forwards and in reverse.

See the Pen motion blur waapi circle by CSS GRID (@cssgrid) on CodePen.

Annoyingly, for those of us familiar with CSS animations, some of the terminologies varies from what we’re used to. Although on the plus side, things are a lot quicker to type!

  • It’s easing rather than animation-timing-function
  • Rather than animation-iteration-count it’s iterations. If we want the animation to repeat forever it’s Infinity rather than infinite. Somewhat confusingly, Infinity isn’t in quotes. Infinity is a JavaScript keyword, whereas the other values are strings.
  • We use milliseconds instead of seconds, which should be familiar to anyone who’s written much JavaScript before. (You can use milliseconds in CSS animations as well, but few people do.)

Let’s take a closer look at one of the options: iterationStart

I was stumped when I first came across iterationStart. Why would you want to start on a specified iteration rather than just decreasing the number of iterations? This option is mostly useful when you use a decimal number. For example, you could set it to .5, and the animation would start half way through. It takes two halves to make a whole, so if your iteration count is set to one and your iterationStart is set to .5, the animation will play from halfway through until the end of the animation, then start at the beginning of the animation and end in the middle! 

It is worth noting that you can also set the total number of iterations to less than one. For example:

var option = {
  iterations: .5,
  iterationStart: .5
}

This would play the animation from the middle until the end. 
endDelay: endDelay is useful if you want to string multiple animations after each other, but want there to be a gap between the end of one animation and the start of any subsequent ones. Here’s a useful video to explain from Patrick Brosset.

Easing

Easing is one of the most important elements in any animation. WAAPI offers us two different ways to set easing — within our keyframes array or within our options object.
In CSS, if you applied animation-timing-function: ease-in-out you might assume that the start of your animation would ease in, and the end of your animation would ease out. In fact, the easing applies between keyframes, not over the entire animation. This can give fine-grained control over the feel of an animation. WAAPI also offers this ability.

var keyframes = [
  { opacity: 0, easing: 'ease-in' }, 
  { opacity: 0.5, easing: 'ease-out' }, 
  { opacity: 1 }
]

It’s worth noting that in both CSS and WAAPI, you shouldn’t pass in an easing value for the last frame, as this will have no effect. This is a mistake a lot of people make.
Sometimes it’s a lot more intuitive to add easing over an entire animation. This is not possible with CSS, but can now be achieved with WAAPI.

var options = {
  duration: 1000,
  easing: 'ease-in-out',
}

You can see the difference between these two kinds of easing in this Pen:

See the Pen Same animation, different easing by CSS GRID (@cssgrid) on CodePen.

Ease vs Linear

It’s worth noting another difference between CSS animation and WAAPI: the default of CSS is ease, while the default of WAAPI is linear. Ease is actually a version of ease-in-out and is a pretty nice option if you’re feeling lazy. Meanwhile, linear is deadly dull and lifeless — a consistent speed that looks mechanical and unnatural. It was probably chosen as the default as it is the most neutral option. However, it makes it even more important to apply an easing when working with WAAPI than when working with CSS, lest your animation look tedious and robotic.

Performance

WAAPI provides the same performance improvements as CSS animations, although that doesn’t mean a smooth animation is inevitable. 

I had hoped that the performance optimizations of this API would mean we could escape the use of will-change and the totally hacky translateZ  —  and eventually, it might. However, at least in the current browser implementations, these properties can still be helpful and necessary in dealing with jank issues.

However, at least if you have a delay on your animation, you don’t need to worry about using will-change. The primary author of the web animations spec had some interesting advice over on the Animation for Work Slack community, which hopefully he won’t mind me repeating here: 

If you have a positive delay, you don’t need will-change since the browser will layerize at the start of the delay and when the animation starts it will be ready to go.

WAAPI Versus CSS Animations?

WAAPI gives us a syntax to do in JavaScript what we could already achieve in a stylesheet. Yet, they shouldn’t be seen as rivals. If we decide to stick to CSS for our animations and transitions, we can interact with those animations with WAAPI.
 

Animation Object

The .animate() method doesn’t just animate our element,  it also returns something. 

var myAnimation = element.animate(keyframes, options);
Animation object viewed in a console

If we take a look at the return value in the console, we’ll see its an animation object. This offers us all sorts of functionality, some of which is pretty self-explanatory, like myAnimation.pause(). We could already achieve a similar result with CSS animations by changing the animation-play-state property, but the WAAPI syntax is somewhat terser than element.style.animationPlayState = "paused". We also have the power to easily reverse our animation with myAnimation.reverse(), which again, is only a slight improvement over changing the animation-direction CSS property with our script.

However, up until now, manipulating @keyframes with JavaScript hasn’t been the easiest thing in the world. Even something as simple as restarting an animation takes a bit of know-how, as Chris Coyier has previously written about. Using WAAPI we can simply use myAnimation.play() to replay the animation from the beginning if it had previously completed, or to play it from mid-iteration if we had paused it.

We can even change the speed of an animation with complete ease.

myAnimation.playbackRate = 2; // speed it up
myAnimation.playbackRate = .4; // use a number less than one to slow it down

getAnimations()

This method will return an array of any animation objects for any animations we’ve defined with WAAPI, as well as for any CSS transitions or animations.

element.getAnimations() // returns any animations or transitions applied to our element using CSS or WAAPI

If you feel comfortable and content using CSS for defining and applying your animations, getAnimations() allows you to use the API in conjunction with @keyframes. It’s possible to continue to use CSS for the bulk of your animation work and still get the benefit of the API when you need it. Let’s see how easy that is.

Even if a DOM element only has one animation applied to it, getAnimations() will always return an array. Let’s grab that single animation object to work with.

var h2 = document.querySelector("h2");
var myCSSAnimation = h2.getAnimations()[0];

Now we can use the web animation API on our CSS animation :)

myCSSAnimation.playbackRate = 4;
myCSSAnimation.reverse();

Promises and Events

We already have a variety of events triggered by CSS that we can utilise in our JavaScript code :  animationstart, animationend, animationiteration and transitionend. I often need to listen for the end of an animation or transition in order to then remove the element it was applied to from the DOM.

The equivalent of using animationend or transitionend for such a purpose in WAAPI would again make use of the animation object:

myAnimation.onfinish = function() {
  element.remove();
}

WAAPI offers us the choice of working with both events and promises. The .finished property of our animation object will return a promise that will resolve at the end of the animation. Here’s what the example above would look like using a promise:

myAnimation.finished.then(() =>
  element.remove())

Let’s look at a slightly more involved example yanked from the Mozilla Developer Network. Promise.all expects an array of promises and will only run our callback function once all of those promises have resolved. As we’ve already seen, element.getAnimations() returns an array of animation objects. We can map over all the animation objects in the array calling .finished on each of them, giving us the needed array of promises.

In this example, it’s only after all the animations on the page have finished that our function will run.

Promise.all(document.getAnimations().map(animation => 
  animation.finished)).then(function() {           
    // do something cool 
  })

The Future

The features mentioned in this article are just the beginning. The current spec and implementation look to be the start of something great.


CSS Animations vs Web Animations API originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/css-animations-vs-web-animations-api/feed/ 14 255477
Animate to Different End States Using One Set of CSS Keyframes https://css-tricks.com/animate-different-end-states-using-one-set-css-keyframes/ https://css-tricks.com/animate-different-end-states-using-one-set-css-keyframes/#comments Fri, 20 Jan 2017 12:34:12 +0000 http://css-tricks.com/?p=250167 I have recently live coded a pure CSS random rainbow particle explosion. There’s a source in the middle of the screen, and rainbow particles shoot out with different speeds at different moments and then fade out. It might seem …


Animate to Different End States Using One Set of CSS Keyframes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
I have recently live coded a pure CSS random rainbow particle explosion. There’s a source in the middle of the screen, and rainbow particles shoot out with different speeds at different moments and then fade out. It might seem like the kind of thing that requires a lot of work and code, but it’s something I did quite quickly and with only 30 lines of SCSS.

GIF of the particle explosion animation.

This writeup is about showcasing one particularly cool trick: there’s just one set of keyframes used to move all those particles on the screen! There’s a second one that’s responsible for fading them. But, even though they end up in completely different positions on the screen, only one set of keyframes is in charge of that.

Let’s see how it all works!

Structure

There’s not much going on here. We just drop 400 particles into the body element. I used Haml because I feel it provides the simplest way of looping that doesn’t even involve a loop variable I’m not going to need anyway. Note that it’s all down to personal preference. I tend to go with the preprocessor that gives me the result I want with the least amount of code because the less I need to write, the better. In this particular case, it happens to be Haml. But, at the end of the day, any preprocessor that lets you generate all the .particle elements in a loop works just fine.

- 400.times do
  .particle

Basic styles

The first thing we do is size our particles and absolutely position. I chose to make them 4x4 squares because I was going for a pixelated look.

$d: 4px;

.particle {
  position: absolute;
  width: $d; height: $d;
}

Random positioning on the screen

We loop through these 400 particles (note that the number in the @for loop in the SCSS needs to be the same number as the one we’ve used in the Haml loop) and we translate them at random x,y points on the screen, where x is between 1vw and 100vw and y is between 1vh and 100vh. random(100) gives us random integers between 1 and 100, both inclusive.

At the same time, we also give these particles different random backgrounds so we can see them. We pick the hsl() format because it’s the most convenient one here. random(360) covers the entire hue wheel giving us random values between 1 and 360.

Hue scale from 0 to 360.

We then max out the saturation (100%) and set for a lightness above 50% (65% in this case).

.particle {
  /* same styles as before */

  @for $i from 0 to 400 {
    &:nth-child(#{$i + 1}) {
      transform: translate(random(100)*1vw, random(100)*1vh);
      background: hsl(random(360), 100%, 65%);
    }
  }
}

We can now see our particles distributed all over the screen:

See the Pen by thebabydino (@thebabydino) on CodePen.

They trigger scrollbars, so we set overflow: hidden on the root element. We also give it a dark background so we can see our light rainbow particles better:

html {
  overflow: hidden;
  background: #222;
}

This way, we get a pretty night sky:

See the Pen by thebabydino (@thebabydino) on CodePen.

Animation

The next step is to animate these particles, making them shoot out from the middle of the screen. This means our animation starts at the 50vw,50vh point:

@keyframes shoot {
  0% { transform: translate(50vw, 50vh); }
}

We don’t specify a final (100%) keyframe. If this is isn’t specified, it gets automatically generated from the styles we have set on the elements we animate – in our case, these are the random translates, different for each and every particle.

We want the motion of the particles to be fast at first and then slow down, which means we need to use an ease out type of timing function. We can just go for the plain old ease-out, or we can use a more advanced one (I go to easings.net for this). We then give our animation a dummy duration of 3s and make it repeat infinitely.

.particle {
  /* same styles as before */
  animation: shoot 3s ease-out infinite;
}

We get the following result:

See the Pen by thebabydino (@thebabydino) on CodePen.

The particles shoot out to different positions in the plane, just like we wanted. But they all animate at once, which isn’t what we want. So the first fix is to give each one of them a different, random animation duration between 1s and 3s within the loop:

.particle {
  /* same styles as before */

    @for $i from 0 to 400 {
        $t: (1 + .01*random(200))*1s;

        &:nth-child(#{$i + 1}) {
            /* same styles as before */
            animation-duration: $t;
        }
    }
}

This is much better:

See the Pen by thebabydino (@thebabydino) on CodePen.

Then we give each particle a random negative delay between 0% and 100% of its animation-duration ($t) in absolute value:

.particle {
  /* same styles as before */

    @for $i from 0 to 400 {
        $t: (1 + .01*random(200))*1s;

        &:nth-child(#{$i + 1}) {
            /* same styles as before */
            animation-delay: -.01*random(100)*$t;
        }
    }
}

See the Pen by thebabydino (@thebabydino) on CodePen.

Finally, we don’t want the particles to just disappear, so we add a second animation (with the same duration and the same delay) to fade them out:

.particle {
  /* same styles as before */
  animation: shoot 0s ease-out infinite;
  animation-name: shoot, fade;
}

@keyframes fade { to { opacity: 0; } }

We now have the final result!

See the Pen by thebabydino (@thebabydino) on CodePen.


Animate to Different End States Using One Set of CSS Keyframes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/animate-different-end-states-using-one-set-css-keyframes/feed/ 9 250167
Swapping State with CSS Keyframes https://css-tricks.com/swapping-state-css-keyframes/ https://css-tricks.com/swapping-state-css-keyframes/#comments Tue, 22 Nov 2016 16:36:55 +0000 http://css-tricks.com/?p=248035 Say you want an element to be in one state for 9 seconds, and in another state for 1 second, on a loop.

No tweening between the state, just a straight swap.

I was wondering how to go about this …


Swapping State with CSS Keyframes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
Say you want an element to be in one state for 9 seconds, and in another state for 1 second, on a loop.

No tweening between the state, just a straight swap.

I was wondering how to go about this other day, and Sarah Drasner showed me that you can use reallllllly short distances between keyframes to move from one state to another. Like 59.999% to 60%. No opportunity to tween there on a normal animation duration like 10 seconds.

See the Pen State Change with Quick Keyframe Change by Chris Coyier (@chriscoyier) on CodePen.

Perhaps a little bit cleaner, you don’t even have to get tricky with the .999 stuff if you use steps, like:

div {
  ...
  animation: color 10s steps(10) infinite both;
}

Single keyframe / Single step changer

That led me to stumble into another super weird CSS thing. You’d think if you used steps(1) that no change at all would occur, right? Since there is only one step? Not true, there is actually 2 steps when you use steps(1), for who-knows-what reasoning. We can use that.

Say we were going for the original premise of this article: one state for 9 seconds, and in another state for 1 second. Like this:

1-1-1-1-1-1-1-1-1-2

You could do:

div {
  ...
  background: orangered;
  animation: color 10s steps(1) infinite both;
}

@keyframes color {
  90% {
    background: teal;
  }
}

That div will only be teal for 1 second! Then switch back to orangered. Change that keyframe to 70% and you’d get:

1-1-1-1-1-1-1-2-2-2

Change it to 10% and you’d get:

1-2-2-2-2-2-2-2-2-2

See the Pen 2efd2dc514da5485073acd6f4b9f3dab by Chris Coyier (@chriscoyier) on CodePen.

Change state and “stay there”

We have a pretty cool trick in hand now for changing state in a timed loop. If you didn’t want the looping part, you could have the animation run once and stay there like:

div {
  ...
  background: orangered;
  animation: color 10s steps(1) forwards;
}

@keyframes color {
  90% {
    background: teal;
  }
}

All we’ve done here is removed the infinite keyword which was making it repeat, and used forwards meaning “when it gets to the end, keep the styles in that final state”. (The both keyword will do that too.)

But how do you swap from one state to another from a user interaction? Even this is possible! Imagine an animation with a super long duration (like, days and days). You could tie a user interaction to jumping around positions (state) within that. Here’s a simple demo of that where the clicking of a link triggers a :target state, which triggers an animation to jump to a position within keyframes that style that state:

See the Pen Keyframe State Changer by Chris Coyier (@chriscoyier) on CodePen.

Just at tricks, folks. We’ll be here all decade.


Swapping State with CSS Keyframes originally published on CSS-Tricks, which is part of the DigitalOcean family. You should get the newsletter.

]]>
https://css-tricks.com/swapping-state-css-keyframes/feed/ 13 248035