DEV Community

Eka
Eka

Posted on • Edited on

Blinking dots: a quick intro to CSS animation

⚠️ Motion Warning: The Glitch page preview contains animated elements.


The CSS animation property makes it possible to create simple UI animation with CSS only. No library to install, import, or load! In addition, most animation libraries build on, or use similar functionalities as, CSS animation; knowing the basic principles of CSS animation helps you learn these animation libraries more efficiently.

This post is a quick introduction to CSS animation to accompany this Glitch page, which I created to demonstrate simple CSS animation in the form of single blinking dots. I'm also including references if you'd like to learn more.

You can view the source code below and/or click "Remix" to try it yourself.

Write your first CSS animation

To animate an element, we need to declare the following:

1) The @keyframes at-rule

@keyframes blink {
  0% { opacity: 0; }
  50% { opacity: 1; }
  100% { opacity: 0; }
}
  • blink is the animation name. You can use any name—just make sure you use the correct name in the animation property.
  • I animate the opacity from 0 to 1 and back to 0. We can[⚠️] animate most CSS properties (except for ones that don't make sense like background-image, that is).
    • If you're curious, here is MDN's list of animatable CSS properties.
    • ⚠️ …But doesn’t mean we should. Animating properties other than transform and opacity is taxing on the browser and may hurt performance. See Chen Hui Jing’s post for a good explanation on how browser rendering engines process styles.

We define animation frames/states either as percentage (like the above example) or from (0%) and to (100%). If several frames have the same styles (like 0% and 100% above), we can group them together as with regular CSS selectors.

Other ways to define animation frames:

/* Use from - to */
@keyframes choppyBlink {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Group same frames together */
@keyframes blink {
  0%, 100% { opacity: 0; } /* more concise! */
  50% { opacity: 1; }
}

This alone will not do anything, though. We need to assign the animation to our element.

2) The animation property

.dot {
  animation: blink 2s infinite;
}
  • Apply the animation named blink to elements with the class .dot.
    • If there is no @keyframes blink in our stylesheets, nothing happens.
  • The animation-duration is 2s. With the code samples above, the element starts with opacity: 0, the opacity gradually increases until it reaches 1 within one second, and goes back to 0 within another second (total of two seconds).
    • Longer duration = slower animation, shorter = faster.
  • The animation-iteration-count is infinite, so it loops forever.
    • If we replace it with 3, our element animates 3 times.
  • I don't declare animation-delay here. By default the delay is 0 second, ie. the animation runs directly as the element is rendered.
    • If we replace it with 4s, the animation starts running 4 seconds after the element is rendered.

Common basic variations:

/* Minimal example */
.dot--basic {
  animation: blink 2s infinite;
}

/* Run animation once */
.dot--once {
  animation: blink 2s 1;
}

/* Wait 4s before running the animation */
.dot--delayed {
  animation: blink 2s infinite 4s;
}

Putting it together, here's the minimal CSS animation example.

@keyframes blink {
  0%, 100% { opacity: 0; }
  50% { opacity: 1; }
}

.dot {
  animation: blink 2s infinite;
}
  • Animate elements with the class name .dot immediately after render
  • Animate from zero to full and back to zero opacity within two seconds (one second each way) forever

But wait... what values exactly can we use in the animation property? Read on!

CSS animation properties

The animation property is a shorthand property to define multiple animation sub-properties, similar to the background property.

So, our example above

.dot--delayed {
  animation: blink 2s infinite 4s;
}

is identical to

.dot--delayed {
  animation-name: blink;
  animation-duration: 2s;
  animation-iteration-count: infinite;
  animation-delay: 4s;
}

There are a total of eight (8) animation sub-properties:

  • ⭐️ animation-name
  • ⭐️ animation-duration (example: 2s)
  • animation-timing-function (example: ease, ease-in-out, linear)
  • ⭐️ animation-delay (example: 2s)
  • ⭐️ animation-iteration-count (infinite or any integer)
  • animation-direction (normal | reverse | alternate | alternate-reverse)
  • animation-fill-mode (none | forwards | backwards | both)
  • animation-play-state (paused | running)

I only discussed the four most common sub-properties here (marked with the ⭐️ emoji), but you can see examples for all sub-properties in MDN Web Docs.

Interestingly, the order of these properties does not matter when you use them in the animation shorthand, except for one rule: animation-duration has to be before animation-delay.

Browser support and vendor prefixes

You may come across CSS animation code that contains vendor prefixes such as -webkit-, -moz-, and so on.

With vendor prefixes, the code from the first example looks like this.

@-webkit-keyframes blink {
  0%, 100% { opacity: 0; }
  50% { opacity: 1; }
}
@-moz-keyframes blink {
  0%, 100% { opacity: 0; }
  50% { opacity: 1; }
}
@-o-keyframes blink {
  0%, 100% { opacity: 0; }
  50% { opacity: 1; }
}
@keyframes blink {
  0%, 100% { opacity: 0; }
  50% { opacity: 1; }
}

.dot {
  -webkit-animation: blink 2s infinite;
  -moz-animation: blink 2s infinite;
  -o-animation: blink 2s infinite;
  animation: blink 2s infinite;
}

At the time of writing, around 96.82% of users worldwide are on browsers that support unprefixed CSS animation, according to caniuse.com. Unsupported browsers will not get any error; they simply will not get the animation (CSS is awesome this way! 😉).

Use your own discretion based on your target users to decide whether to include vendor prefixes. (If you use any kind of preprocessor, these will be automatically added based on your configuration.)

Make sure it's accessible

1. Turn off our animation for users who prefer reduced motion

Users may specify their preference for reduced motion in their device's settings, for example because it triggers motion sickness.

If your animation is not crucial to the UI, disable it. If it's crucial (ie. conveys something meaningful), define an animation with the minimum movement possible.

@media (prefers-reduced-motion: reduce) {
  .dot {
    animation: none; /* or define an alternate animation */
  }
}

2. Add appropriate ARIA role/attribute if necessary

If you create an extra element for animation that has no meaningful content, add aria-hidden="true" so screen readers could skip it.

If the animated element functions as an image, for instance a pulsating font icon or element with background image, add role="img" and aria-label attributes, for example:

<div class="icon icon-star-bg" role="img" aria-label="Favorite"></div>

Conclusion

I'm only covering the surface here, but hopefully enough to get you familiar with CSS animation. My Glitch demo page only contains basic examples using opacity and transform. Remix it (or create a new one) and get creative with the animations!


Further reads

Top comments (3)

Collapse
 
roblevintennis profile image
Rob Levin • Edited

Nice post!

So, I did mine quite similar but slightly different using background-color for my use case. But, I think there's a slight tweak for the reduced motion that's worth pointing out:

@keyframes blink {
  50% {
    background-color: transparent;
  }
}

@media (prefers-reduced-motion), (update: slow) {
  .loader,
  .loader::before,
  .loader::after {
    transition-duration: 0.001ms !important;
  }
}
Enter fullscreen mode Exit fullscreen mode

The update: slow is for devices that cannot handle the animation (smashingmagazine.com/2021/10/respe... aka targeting a screen with a low refresh rate.

And the alternative to full removal of the animation is talked about here:

css-tricks.com/revisiting-prefers-...

Collapse
 
sarahcodes_dev profile image
Sarah 🦄

Great post, I didn't know about the media query for reduced motion or even to consider this as an accessibility concern, good to know!

Collapse
 
ekafyi profile image
Eka

Glad it helped! I'd normally just put that media query in the global CSS with the universal selector * and override if needed.