Animations can be an important part of a web page’s design. Used appropriately, they can enhance the user experience, and add a nice polishing touch. But at the same time, animations come at a cost. If done improperly, they could lead to unwanted jank, something we all want to avoid.

Sometimes performance issues can be resolved by being careful about which properties we animate. For instance, changing an element’s position via translate instead of top or left keeps the browser from continuously going through the layout and paint steps when rendering. But how about those times where the animation is going to require a repaint. In those cases, how can we optimize these animations so they don’t wreak havoc on the page’s performance?

New Layers

One approach is to offload the animation to the GPU by moving the animated elements to their own layers. This allow the GPU to render them, and since it’s designed specifically for processing graphics, the visual result is often much smoother.

A (once) popular way of forcing this to happen is to use the translateZ or translate3d properties on the element. Since these are 3d related properties, the browser moves the element to its own layer, and thus pulls in the GPU to aid in rendering.

Typically, this would be done by setting transform:translateZ(0) or transform:translate3d(0,0,0) on an element.

Downside

And although this can work well in certain circumstance, there are also some risks when we start considering lower-end devices with fewer resources.

Desktop machines, for instance, can handle pages that have several layers because the GPU often has the resources it needs to handle the processing demands. Lower-end devices, however, have limited resources for their GPU processing. Which means using this technique too often could lead to overwhelming the resources of the device, resulting in performance degradation—something none of us wants.

This doesn’t mean we should absolutely avoid this technique, but rather that using it should be done with care.

Will-Change

Another option that has come along in recent years is the will-change property. It’s currently supported by all major browsers except IE/Edge, and is used to offload the decision of how to optimize the animated elements to the browser. It’s essentially a hint to the browser that some kind of change will be taking place, and to plan accordingly. But instead of forcing the browser to create separate layers, the browser can determine the how and when of the optimization.

The property signals to the browser that something about the element will be changing soon. The change could be related to the ‘scroll-position’ or ‘content’ of the element, but can also refer to the specific properties which will be animated (e.g. ‘transform’, ‘opacity’, etc.).

.element {
  will-change: transform;
}

Best Practices

Although will-change has a lot of potential benefits, there are a few best practices to keep in mind when using it:

  • Only use it on the elements or properties that need it. Using it on everything is going to create more problems than it solves. The browser is already trying to optimize the page as best it can. By using will-change too frequently, the browser may try to reserve too many resources for these potential animations than it actually needs, resulting in degraded performance.

  • Make sure the browser has time to optimize. This means making sure the will-change property is declared far enough in advanced to give the browser an opportunity to optimize. A classic example would be something like this:

    .element {
      /* other styles */
      transition: transform 1s;
    }
    
    .element:hover {
      will-change: transform;
    }
    
    .element:active {
      transform: rotate(-10deg) scale(1.2);
    }
    

    In this case, will-change only is called when the element is being hovered, which gives the browser a chance to optimize for any animations that would take place on click. A similar approach could target the element’s parent, declaring the will-change property only when the parent is being hovered.

      .element {
        /* other styles */
        transition: transform 1s;
      }
    
      .element-parent:hover .element {
        will-change: transform;
      }
    
      .element:hover {
        transform: rotate(-10deg) scale(1.2);
      }
    
  • Remove will-change when not needed. Just like we should only use will-change just before it’s needed, we should also be looking to make sure it’s removed as soon as it’s no longer needed. This is because the browser will hold on to resources to make the optimization, and so if they’re no longer needed, it would be in our best interest to let the browser know that.

    Although the above examples show how this can be done in the stylesheet (e.g. having it applied only on the hover state of an element), the recommended route is to add the will-change property via JavaScript.

    An example: (source)

var el = document.getElementById('element');

// Set will-change when the element is hovered
el.addEventListener('mouseenter', hintBrowser);
el.addEventListener('animationEnd', removeHint);

function hintBrowser() {
  // The optimizable properties that are going to change
  // in the animation's keyframes block
  this.style.willChange = 'transform, opacity';
}

function removeHint() {
  this.style.willChange = 'auto';
}

Use Judiciously

In both approaches (translateZ or will-change), the key is to test the performance and only use these techniques when:

  • The elements in question have a measurably high repaint cost; and
  • Utilizing these methods actually help the overall performance

When assessing overall performance, it’s important to keep an eye on mobile performance. These techniques can help the rendering on more powerful devices by pushing a lot of the work to the GPU. But if used inappropriately on lower-end, less powerful, devices, this additional work can actually make things worse.

Which means, as in most things, use them judiciously, testing the results to make sure they’re helping and not hurting.

Resources