Tunnel of light
Tunnel of light (source: plBrYK)

When animating elements to make them move around the screen, you want the animation to look smooth. You want it to run at 60 frames per second and run without showing any jittering or tearing. All of that seems straightforward, but it becomes less so when you think about all the various devices your content will be viewed on:

Some devices like laptops and desktops are powerful and have no problems with whatever animation we throw at it, whereas some devices like mobile phones are less powerful and require additional care. What we want to do is ensure that all devices can view our animation with all the performance bells and whistles users expect (and maybe even demand!). In this post, I’ll show you an awesome trick for doing just that.

Onwards!

More GPU, Please!

Before we get to lines of markup and code, let’s talk about the approach we want to use for performantly moving elements around the screen. You may think that since we are moving elements around – a very visual task – that our device’s graphics card (a.k.a. GPU) will do all the heavy lifting. As it turns out, in many cases that isn’t how it works. The CPU, in-between juggling the many demands placed on it, ends up dealing with moving pixels around the screen as well. But that approach is inefficient, and we don’t want to use it. We want our GPU to do all of the “moving elements around the screen” work.

While making that happen sounds tricky, all you need to do is tell your GPU to do the work. There are several ways of doing that "telling"; one of the easiest is to use the translate3d transform. Don't let the "3d" in the name deceive you: While you can totally use this transform for animating in 3D space, this transform also works perfectly for the more common 2D cases where we only set the x and y positions.

Meet the translate3d Transform

You use this transform by providing three arguments - the horizontal position (x), vertical position (y), and depth (z). Here is an example:


#myCircle {
   transform: translate3d(150px, 100px, 0);
}

In this example, our myCircle element is located at (150, 100). The value for depth is kept at 0, since we don’t really care about that – we just need to specify it to keep our browser from complaining. When this transform gets applied, our myCircle element will be personally catered to by the GPU. We’ll look into why that is so a bit later, but here’s a hint: It has to do with the z-value of 0.

While we think of the transform property as being a CSS-only feature, its application goes beyond its boundaries. You will commonly animate an element’s position in JavaScript as well. What we’ve seen with translate3d in CSS also applies in the crazy world of JavaScript.

Here’s how to set the translate3d transform on our myCircle element in JavaScript:


var myCircle = document.querySelector(“#myCircle”);
setTranslate(150, 100, myCircle);
 
function setTranslate(xPos, yPos, el) {
	el.style.transform = "translate3d(" + xPos + ", " + yPos + "px, 0)";
}

There are a lot of quotation marks and plus symbols when we try to recreate the translate3d values in code. If you want a more modern ES6 implementation that avoids some of that, you can simplify our setTranslate function down to the following:


function setTranslate(xPos, yPos, el) {
	el.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;
}

There are probably many other ways that get the same end result, so use whatever approach you are comfortable with that ends up applying the translate3d transform on the elements you are animating.

Why does this work?

We’ve seen the technical pieces needed to get our GPU to do the heavy lifting when dealing with an element whose position we want to animate. Now let’s take a few steps back and talk about why this works.

Our browsers have an ever-evolving set of rules that they use when determining whether an element will be handled by the CPU or GPU. One of the rules states that if you manipulate any elements in 3D space, those elements will be handled by the GPU. What we’ve done to take advantage of that rule by using the translate3d function and setting a the depth (a.k.a. the Z value) to a default of 0.

You can use any of the various 3D-related transforms CSS provides to achieve a similar result. You don’t have to use translate3d, but it certainly is a convenient one. One common variation is where we just set the translateZ transform to a value of 0 and go about our business as usual:


#myRandomContent {
   transform: translateZ(0);
}

This ensures that our myRandomContent element is handled by the GPU thanks to the translateZ transform. All of this sounds pretty awesome, so there has to be a catch, right?

The catch is that sometimes forcing an element to use the GPU can result in worse performance. Pushing an element to the GPU takes extra memory, and it causes a short lag while your CPU/GPU/memory negotiate some details regarding the pushing. In most cases, both of these problems are completely unnoticeable. This is where testing your animation across a variety of devices is extremely helpful. Fortunately, you will have some technical help with this: Almost all browsers provide developer tools that help you analyze when pixels get painted to the screen, help you determine whether your GPU is doing anything, and more.

Some Final Words

It’s rare you get to have cake (with frosting) and eat it too. Happily, the translate3d transform is one such moment. Not only does it allow you to move your elements around the screen, it lets you do so in style with the GPU in the driver’s seat. The one caveat is that there will be rare cases where making your GPU do work is not the right course of action. To help us avoid having to figure out when the GPU should do work versus when the CPU should, browsers have introduced support for a CSS property called will-change.

The will-change property allows you to specify which properties of an element you anticipate changing and animating. Your browser will then figure out what the right optimization needs to be. Here is an example of it at work:


#myButton {
   will-change: transform;
}
 
#myButton:hover {
   transform: scale(1.5, 1.5);
}

Overall, the jury is still out on whether this is a better approach than web developers and designers specifying what needs to happen. You can probably guess where I stand on this, and Paul Lewis has offered his views, which you should definitely check out.

This article was heavy on code and theory but low on examples. To see some examples of the translate3d transform at work, check out this sliding menu that uses it in CSS or this parallax example that uses it in JavaScript.

Article image: Tunnel of light (source: plBrYK).