Animations

index


layout: collection override:tags: [] date: 2020-10-06 pathName: animations

Examples of high-performance CSS animations

In this post find out how some popular animations found on CodePen have been created. These animations all use the performant techniques discussed in other articles in this section.

See Why are some animations slow? to learn the theory behind these recommendations, and the Animations Guide for practical tips.

Wizard loading animation

{% IFrame { src: 'https://codepen.io/Craaftx/embed/ExyYRMK?height=458&theme-id=light&default-tab=result', title: 'Only CSS Loader - Wizard by Guilmain Dorian' } %}

View Wizard loading animation on CodePen

This loading animation is built entirely with CSS. The image plus all of the animation has been created in CSS and HTML, no images or JavaScript. To understand how it was created and how well it performs you can use Chrome DevTools.

Inspect the animation with Chrome DevTools

With the animation running, open the Performance tab in Chrome DevTools and record a few seconds of the animation. You should see in the Summary that the browser is not doing any Layout or Paint operations when running this animation.

{% Img src="image/admin/r1h4gb24ZiYXAfI7Mskh.jpg", alt="Summary in DevTools", width="724", height="416" %}
The summary after profiling the wizard animation.

To find out how this animation was achieved without causing layout and paint, inspect any of the moving elements in Chrome DevTools. You can use the Animations Panel to locate the various animated elements, clicking on any element will highlight it in the DOM.

{% Img src="image/admin/sPoemcfld1jfUkSFhv3o.jpg", alt="The Animations Panel showing the various parts of our animation.", width="800", height="349" %}
Viewing and selecting items in the Chrome DevTools Animation Panel.

For example select the triangle, and watch how the box of the element transforms during its journey into the air, as it spins, and then returns to the start position.

{% Video src="video/tcFciHGuF3MxnTr1y5ue01OGLBn2/STzIqse0ekWT49oJbwX5.mp4", controls=true, autoplay=true, loop=true, muted=true %}
Video showing how we can track the path of the triangle in Chrome DevTools.

With the element still selected look at the Styles Panel. There you can see the CSS which draws the shape of the triangle, and the animation being used.

How it works

The triangle is created by using the ::after pseudo-element to add generated content, using borders to create the shape.

.triangle {
    position: absolute;
    bottom: -62px;
    left: -10px;
    width: 110px;
    height: 110px;
    border-radius: 50%;
}

.triangle::after {
    content: "";
    position: absolute;
    top: 0;
    right: -10px;
    width: 0;
    height: 0;
    border-style: solid;
    border-width: 0 28px 48px 28px;
    border-color: transparent transparent #89beb3 transparent;
}

{% Aside %} You can find out more about making shapes with borders and generated content in The Shapes of CSS. {% endAside %}

The animation is added with the following line of CSS,

animation: path_triangle 10s ease-in-out infinite;

Staying in Chrome DevTools you can find the keyframes by scrolling down the Style Panel. There you will find that the animation is created by using transform to change the position of the element and rotate it. The transform property is one of the properties described in the Animations Guide, which does not cause the browser to do layout or paint operations (which are the main causes of slow animations).

@keyframes path_triangle {
  0% {
    transform: translateY(0);
  }
  10% {
    transform: translateY(-172px) translatex(10px) rotate(-10deg);
  }
  55% {
    transform: translateY(-172px) translatex(10px) rotate(-365deg);
  }
  58% {
    transform: translateY(-172px) translatex(10px) rotate(-365deg);
  }
  63% {
    transform: rotate(-360deg);
  }
}

{% Glitch 'animation-breakdown1-2' %}

Each of the different moving parts of this animation uses similar techniques. The result is a complex animation which runs smoothly.

Pulsating Circle

{% IFrame { src: 'https://codepen.io/peeke/embed/BjxXZa?height=458&theme-id=light&default-tab=result', title: 'Pulsating Circle by Peeke' } %}

View Pulsating Circle on CodePen

This type of animation is sometimes used to draw attention to something on a page. To understand the animation you can use Firefox DevTools.

Inspect the animation with Firefox DevTools

With the animation running, open the Performance tab in Firefox DevTools and record a few seconds of the animation. Stop the recording, in the waterfall you should see that there are no entries for Recalculate Style. You now know that this animation does not cause style recalculation, and therefore layout and paint operations.

{% Img src="image/admin/68jWlrbNhgmS07vrXMCO.jpg", alt="details of the animation in the Firefox Waterfall", width="800", height="354" %}
The Firefox DevTools Waterfall.

Staying in Firefox DevTools inspect the circle to see how this animation works. The <div> with a class of pulsating-circle marks the position of the circle, however it does not draw a circle itself.

.pulsating-circle {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translateX(-50%) translateY(-50%);
    width: 30px;
    height: 30px;
}

The visible circle and animations are achieved using the ::before and ::after pseudo-elements.

The ::before element creates the opaque ring that extends outside of the white circle, using an animation called pulse-ring, which animates transform: scale, and opacity.

.pulsating-circle::before {
    content: '';
    position: relative;
    display: block;
    width: 300%;
    height: 300%;
    box-sizing: border-box;
    margin-left: -100%;
    margin-top: -100%;
    border-radius: 45px;
    background-color: #01a4e9;
    animation: pulse-ring 1.25s cubic-bezier(0.215, 0.61, 0.355, 1) infinite;
}

@keyframes pulse-ring {
  0% {
    transform: scale(0.33);
  }
  80%, 100% {
    opacity: 0;
  }
}

Another way to see which properties are being animated is to select the Animations panel in Firefox DevTools. You will then see a visualization of the animations being used, and the properties that are being animated.

{% Video src="video/tcFciHGuF3MxnTr1y5ue01OGLBn2/FuxnywlwqojS8YVMCmKC.mp4", controls=true, autoplay=true, loop=true, muted=true %}
With the ::before pseudo-element selected we can see which properties are animating.

The white circle itself is created and animated using the ::after pseudo-element. The animation pulse-dot uses transform: scale to grow and shrink the dot during the animation.

.pulsating-circle::after {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  display: block;
  width: 100%;
  height: 100%;
  background-color: white;
  border-radius: 15px;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
  animation: pulse-dot 1.25s cubic-bezier(0.455, 0.03, 0.515, 0.955) -0.4s infinite;
}

@keyframes pulse-dot {
  0% {
    transform: scale(0.8);
  }
  50% {
    transform: scale(1);
  }
  100% {
    transform: scale(0.8);
  }
}

An animation like this could be used in various places in your application, it's important that these small touches don't impact the overall performance of your app.

Pure CSS 3D Sphere

{% IFrame { src: 'https://codepen.io/iamlark/embed/jYzYJg?height=458&theme-id=light&default-tab=result', title: 'Pure CSS 3d Sphere' } %}

View Pure CSS 3D Sphere on CodePen

This animation seems incredibly complicated, however it uses techniques that we have already seen in the previous examples. The complexity comes from animating a large number of elements.

Open Chrome DevTools and select one of the elements with a class of plane. The sphere is made up of a set of rotating planes and spokes.

{% Video src="video/tcFciHGuF3MxnTr1y5ue01OGLBn2/8lA3linRM6SqufzlUlJR.mp4", controls=true, autoplay=true, loop=true, muted=true %}
The plane appears to be rotating.

{% Aside %} The DOM Search Tool in Chrome DevTools can make it easier to find an element that you want to inspect. {% endAside %}

These planes and spokes are inside a wrapper <div>, and it is this element which is rotating using transform: rotate3d.

.sphere-wrapper {
  transform-style: preserve-3d;
  width: 300px;
  height: 300px;
  position: relative;
  animation: rotate3d 10s linear infinite;
}

@keyframes rotate3d {
  0% {
    transform: rotate3d(1, 1, 1, 0deg);
  }
  25% {
    transform: rotate3d(1, 1, 1, 90deg);
  }
  50% {
    transform: rotate3d(1, 1, 1, 180deg);
  }
  75% {
    transform: rotate3d(1, 1, 1, 270deg);
  }
  100% {
    transform: rotate3d(1, 1, 1, 360deg);
  }
}

The dots can be found nested inside the plane and spoke elements, they use an animation which uses transform to scale and translate them. This creates the pulsing effect.

{% Video src="video/tcFciHGuF3MxnTr1y5ue01OGLBn2/JoLi8L3VW9nUG25sEvkZ.mp4", controls=true, autoplay=true, loop=true, muted=true %}
The dot rotates with the sphere and pulses.
.spoke-15 .dot,
.spoke-21 .dot {
  animation: pulsate 0.5s infinite 0.83333333s alternate both;
  background-color: #55ffee;
}

@-webkit-keyframes pulsate {
  0% {
    transform: rotateX(90deg) scale(0.3) translateZ(20px);
  }
  100% {
    transform: rotateX(90deg) scale(1) translateZ(0px);
  }
}

The work involved in creating this animation has been to get the timing right, to create the turning and pulsing effect. The animations themselves are quite straightforward, and use methods which perform very well.

You can see how this animation performs by opening Chrome DevTools and recording Performance while it is running. After the initial load, the animation is not triggering Layout or Paint, and runs smoothly.

Conclusion

From these examples you can see how animating a few properties using performant methods can create some very cool animations. By defaulting to the performant methods described in the Animations guide you can spend your time creating the effect you want, with fewer concerns about making the page slow.

How to create high-performance CSS animations

This guide teaches you how to create high-performance CSS animations.

See Why are some animations slow? to learn the theory behind these recommendations.

Browser compatibility

All of the CSS properties that this guide recommends have good cross-browser support.

Move an element {: #move }

To move an element, use the translate or rotation keyword values of the transform property.

For example to slide an item into view, use translate.

.animate {
  animation: slide-in 0.7s both;
}

@keyframes slide-in {
  0% {
    transform: translateY(-1000px);
  }
  100% {
    transform: translateY(0);
  }
}

{% Glitch 'animation-slide-in' %}

Items can also be rotated, in the example below 360 degrees.

.animate {
  animation: rotate 0.7s ease-in-out both;
}

@keyframes rotate {
  0% {
    transform: rotate(0);
  }
  100% {
    transform: rotate(360deg);
  }
}

{% Glitch 'animation-rotate' %}

Resize an element {: #resize }

To resize an element, use the scale keyword value of the transform property.

.animate {
  animation: scale 1.5s both;
}

@keyframes scale {
  50% {
    transform: scale(0.5);
  }
  100% {
    transform: scale(1);
  }
}

{% Glitch 'animation-scale' %}

Change an element's visibility {: #visibility }

To show or hide an element, use opacity.

.animate {
  animation: opacity 2.5s both;
}

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

{% Glitch 'animation-opacity' %}

{% Aside %} Find copy and paste examples of various animations at Animista. {% endAside %}

Avoid properties that trigger layout or paint {: #triggers }

Before using any CSS property for animation (other than transform and opacity), determine the property's impact on the rendering pipeline. Avoid any property that triggers layout or paint unless absolutely necessary.

{% Aside 'warning' %} If you must use a property that triggers layout or paint, it is unlikely that you will be able to make the animation smooth and high-performance. {% endAside %}

Force layer creation {: #force }

As explained in Why are some animations slow?, by placing elements on a new layer they can be repainted without also requiring the rest of the layout to be repainted.

Browsers will often make good decisions about which items should be placed on a new layer, but you can manually force layer creation with the will-change property. As the name suggests, this property tells the browser that this element is going to be changed in some way.

{% Aside 'caution' %} As layer creation can cause other performance issues, this property should not be used as a premature optimization. Instead, you should only use it when you are seeing jank and think that promoting the element to a new layer may help. {% endAside %}

In CSS this property can be applied to any selector:

body > .sidebar {
  will-change: transform;
}

However, the specification suggests this approach should only be taken for elements that are always about to change. If the above example was a sidebar the user could slide in and out, that might be the case. Some items on your page may not frequently change, and so it would be better to apply will-change using JavaScript at a point where it becomes likely the change will occur. You'll need to make sure to give the browser enough time to perform the optimizations needed and then remove the property once the changing has stopped.

{% Aside %} For more information and examples of correct use of will-change read Everything You Need To Know About The CSS will-change Property. {% endAside %}

If you need a way to force layer creation in one of the rare browsers that doesn't support will-change (most likely Internet Explorer at this point), you can set transform: translateZ(0).

Debug slow or janky animations {: #debug }

Chrome DevTools and Firefox DevTools have lots of tools to help you figure out why your animations are slow or janky.

Check if an animation triggers layout {: #layout }

An animation that moves an element using something other than transform, is likely to be slow. In the following example, I have achieved the same visual result animating top and left, and using transform.

{% Compare 'worse' %}

.box {
  position: absolute;
  top: 10px;
  left: 10px;
  animation: move 3s ease infinite;
}

@keyframes move {
  50% {
     top: calc(90vh - 160px);
     left: calc(90vw - 200px);
  }
}

{% endCompare %}

{% Compare 'better' %}

.box {
  position: absolute;
  top: 10px;
  left: 10px;
  animation: move 3s ease infinite;
}

@keyframes move {
  50% {
     transform: translate(calc(90vw - 200px), calc(90vh - 160px));
  }
}

{% endCompare %}

You can test this in the following two Glitch examples, and explore performance using DevTools.

Chrome DevTools {: #layout-chrome }

  1. Open the Performance panel.
  2. Record runtime performance while your animation is happening.
  3. Inspect the Summary tab.

If you see a nonzero value for Rendering in the Summary tab, it may mean that your animation is causing the browser to do layout work.

{% Img src="image/admin/cMNQR2jBEwa6ku5POXtZ.jpg", alt="The Summary panel shows 37ms for rendering and 79ms for painting.", width="800", height="699" %}
The animation-with-top-left example causes rendering work.
{% Img src="image/admin/3bn44P9h6lR93uBNRXY3.jpg", alt="The Summary panel show zero values for rendering and painting.", width="800", height="639" %}
The animation-with-transform example does not cause rendering work.

Firefox DevTools {: #layout-firefox }

In Firefox DevTools the Waterfall can help you to understand where the browser is spending time.

  1. Open the Performance panel.
  2. In the panel Start Recording Performance while your animation is happening.
  3. Stop the recording and inspect the Waterfall tab.

If you see entries for Recalculate Style then the browser is having to begin at the start of the rendering waterfall.

Check if an animation is dropping frames {: #fps }

  1. Open the Rendering tab of Chrome DevTools.
  2. Enable the FPS meter checkbox.
  3. Watch the values as your animation runs.

At the top of the FPS meter UI you see the label Frames. Below that you see a value along the lines of 50% 1 (938 m) dropped of 1878. A high-performance animation will have a high percentage, e.g. 99%. A high percentage means that few frames are being dropped and the animation will look smooth.

{% Img src="image/admin/i9Cg7nswyO7jB768kpdQ.jpg", alt="The fps meter shows 50% of frames were dropped", width="710", height="469" %}
The animation-with-top-left example causes 50% of frames to be dropped
{% Img src="image/admin/FGROZ0i15tCAoiIOoEdG.jpg", alt="The fps meter shows only 1% of frames were dropped", width="710", height="468" %}
The animation-with-transform example causes only 1% of frames to be dropped.

Check if an animation triggers paint {: #paint }

When it comes to painting, some things are more expensive than others. For example, anything that involves a blur (like a shadow, for example) is going to take longer to paint than drawing a red box. In terms of CSS, however, this isn't always obvious: background: red; and box-shadow: 0, 4px, 4px, rgba(0,0,0,0.5); don't necessarily look like they have vastly different performance characteristics, but they do.

Browser DevTools can help you to identify which areas need to be repainted, and performance issues related to painting.

Chrome DevTools {: #paint-chrome }

  1. Open the Rendering tab of Chrome DevTools.
  2. Select Paint Flashing.
  3. Move the pointer around the screen.
{% Img src="image/admin/MzAeQc5PvCltcm3gWaNV.jpg", alt="A UI element highlighted in green to demonstrate it will be repainted", width="708", height="185" %}
In this example from Google Maps you can see the elements that will be repainted.

If you see the whole screen flashing, or areas that you don't think should change highlighted then you can do some investigation.

If you need to dig into whether a particular property is causing performance issues due to painting, the paint profiler in Chrome DevTools can help.

Firefox DevTools {: #paint-firefox }

  1. Open Settings and add a Toolbox button for Toggle paint flashing.
  2. On the page you want to inspect, toggle the button on and move your mouse or scroll to see highlighted areas.

Conclusion

Where possible restrict animations to opacity and transform in order to keep animations on the compositing stage of the rendering path. Use DevTools to check which stage of the path is being affected by your animations.

Use the paint profiler to see if any paint operations are particularly expensive. If you find anything, see if a different CSS property will give the same look and feel with better performance.

Use the will-change property sparingly, and only if you encounter a performance issue.

Why are some animations slow?

Modern browsers can animate two CSS properties cheaply: transform and opacity. If you animate anything else, the chances are you're not going to hit a silky smooth 60 frames per second (FPS). This post explains why this is the case.

Animation performance and frame rate {: #fps }

It is widely accepted that a frame rate of 60 FPS is the target when animating anything on the web. This frame rate will ensure that your animations look smooth. On the web a frame is the time that it takes to do all of the work required to update and repaint the screen. If each frame does not complete within 16.7ms (1000ms / 60 ≈ 16.7), then users will perceive the delay.

The rendering pipeline {: #pipeline }

To display something on a webpage the browser has to go through the following sequential steps:

  1. Style: Calculate the styles that apply to the elements.
  2. Layout: Generate the geometry and position for each element.
  3. Paint: Fill out the pixels for each element into layers.
  4. Composite: Draw the layers to the screen.

These four steps are known as the browser's rendering pipeline.

When you animate something on a page that has already loaded these steps have to happen again. This process begins from the step that has to be changed in order to allow the animation to take place.

As mentioned before, these steps are sequential. For example, if you animate something that changes layout, the paint and composite steps also have to run again. Animating something that changes layout is therefore more expensive than animating something that only changes compositing.

{% Aside %} For an in-depth look at exactly how this process happens in the browser read From Braces to Pixels and Inside look at modern browser browser. {% endAside %}

Animating layout properties {: #layout }

Layout changes involve calculating the geometry (position and size) of all the elements affected by the change. If you change one element, the geometry of other elements may need to be recalculated. For example, if you change the width of the <html> element any of its children may be affected. Due to the way elements overflow and affect one another, changes further down the tree can sometimes result in layout calculations all the way back up to the top.

The larger the tree of visible elements, the longer it takes to perform layout calculations.

Animating paint properties {: #paint }

Paint is the process of determining in what order elements should be painted to the screen. It is often the longest-running of all tasks in the pipeline.

The majority of painting in modern browsers is done in software rasterizers. Depending on how the elements in your app are grouped into layers, other elements besides the one that changed may also need to be painted.

Animating composite properties {: #composite }

Compositing is the process of separating the page into layers, converting the information about how the page should look into pixels (rasterization), and putting the layers together to create a page (compositing).

This is why the opacity property is included in the list of things which are cheap to animate. As long as this property is in its own layer, changes to it can be handled by the GPU during the compositing step. Chromium-based browsers and WebKit create a new layer for any element which has a CSS transition or animation on opacity.

{% Aside %} For an in-depth look at compositing see the article GPU Animation: Doing It Right {% endAside %}

What is a layer? {: #layers }

By placing the things that will be animated or transitioned onto a new layer, the browser only needs to repaint those items and not everything else. You may be familiar with Photoshop's concept of a layer which contains a bunch of elements that can be moved together. Browser rendering layers are similar to that idea.

While the browser does a good job of making decisions about what elements should be on a new layer, if it misses one there are ways to force layer creation. You can find out about that in How to create high-performance animations. However, creating new layers should be done with care because each layer uses memory. On devices with limited memory creating new layers may cause more of a performance problem than the one you are trying to solve. In addition, each layer's textures need to be uploaded to the GPU. Therefore you may well hit constraints of bandwidth between the CPU and GPU.

{% Aside %} You can read a good explanation of layers, and how to create them in Layers and how to force them. {% endAside %}

CSS vs JavaScript performance {: #css-js }

You might wonder: is it better from a performance perspective to use CSS or JavaScript for animations?

CSS-based animations, and Web Animations (in the browsers that support the API), are typically handled on a thread known as the compositor thread. This is different from the browser's main thread, where styling, layout, painting, and JavaScript are executed. This means that if the browser is running some expensive tasks on the main thread, these animations can keep going without being interrupted.

As explained in this article, other changes to transforms and opacity can, in many cases, also be handled by the compositor thread.

If any animation triggers paint, layout, or both, the main thread will be required to do work. This is true for both CSS and JavaScript animations, and the overhead of layout or paint will likely dwarf any work associated with CSS or JavaScript execution, rendering the question moot.