Want to get your CSS animations to run at 60 frames per second?
If you’ve been working with CSS3 transforms and transitions, you’ve likely come across the issue of hardware acceleration. This is a feature of the browser that allows certain graphics operations to run many times faster than their non-accelerated equivalents. Done right, this can result in high frame rate, silky smooth animation that can actually save battery life.
Right now, tapping into HW acceleration is a bit if a dark art; if you’ve seen incantations like -webkit-transform: translateZ(0px) – you may have wondered how such an apparently meaningless instruction can cause a div to render faster. And why does it cause some operations to be slower?
The answers are really pretty simple, but they involve stepping out of the standard CSS world and learning a little bit (very little, I promise) about how graphics hardware works. You probably know or have heard bits of this already, but hopefully looking at the whole picture will make it easier for you to design high-performance HTML5 and CSS3 web pages.
Lots of details. So how should we think about this when architecting our web UX?
The first thing to realize is that the GPU in a browser is only used for a few different effects, and the ones to pay attention to are transforms (-webkit-transform,) transitions, and translucency. This will surely grow over time; color fills and gradients are very “acceleratable,” but it’s up to the browser vendors to do it. For now, focus on the transforms as a) they are super powerful and cool and b) they will help you understand how it works.
And here is how it works, simplified:
- the browser looks through your web page and finds DIVs that either have a (-webkit-) transform or are translucent
- each such DIV – let’s call them ‘accelerated DIV’ is marked as “turn into a surface and send to the GPU”
- other DIVs are chopped into surfaces based on size and layering (not important)
- all surfaces are filled in and sent to the GPU
- the GPU layers all the surfaces properly and builds the final image
So far, none of this has resulted in a tremendous amount of speed except that last bit: taking a hundred surfaces (think of layers in Photoshop) and compositing them together with translucency is very very fast on a GPU.
Where we really get speed is next: once you’ve sent a DIV to the GPU, if you want to move it in 2D or 3D, you can tell the GPU “rotate this by 2 degrees” and it will happen virtually instantaneously. You don’t have to send it to the GPU again. The CPU will not even lift a finger, and your device battery will be spared. This is why transforms are really fast and efficient.
Gotcha: Some things have to happen on the CPU. Text is a classic example (although this is changing.) If you’re going to put some text in a div and fly it around, great. But if you’re going to change that text as it flies around, you’re making it hard for the CPU to get out of the GPUs way. So if you transform a div without changing what’s in it, it will be lightning fast, but if you change the contents of the div while transforming it, you can cause it to be brought to the CPU, filled with the new content, sent back to the GPU, and then transformed. This is not at fast.
Simple rule: transforming DIVs without changing their content will be fast. Changing things inside the DIV may or may not slow things back down.
Aside from changing the contents of a DIV, changing the shape of the DIV can cause it to be re-sent to the GPU. A typical example of this is changing the height or width property. Since this needs a different-sized surface, current browsers appear to allocate a new surface, fill it, and then send that over since there is a different number of pixels in the surface. Note that this is different from using transform: scaleY. Scaling happens on the GPU and doesn’t affect the contents and can be fully. I think of this as happening the “outside” of the surface, whereas changing height affects what’s inside. It’s not important to mull this deeply; consider it an example of what to watch out for.
A little more about that transformZ(0px) trick we mentioned earlier. This has been recommended broadly as a way to make a div faster. The reason this works is because any transform property marks the DIV as an “acceleratable” surface; it will be sent to the GPU independently. This means even properties like “top:” can be faster. There’s a caveat, though: when you first apply a transform, there is a tiny stall as the div is sent to the GPU. Don’t go from “non-transformed” to “transformed” at the last minute. I came across this in an app where dozens of objects moved quickly in response to an event. Initially I applied the transform when the event fired. This resulted in a nasty “hiccup” when the animation started. I think applied a “null” transform (transformZ(0px)) in the default style of the items, with the result that when my event fired, the DIVs were already copied to the GPU. Result: hair-trigger responsiveness. Nice.
If you’ve made it this far, congratulations – it can seem quite complicated. This has been a very light sketch of a complicated issue that is evolving rapidly. Used properly, however, hardware acceleration is extremely powerful and usable right now and can deliver real benefits – responsive UI and longer battery life – to users today.
The future is bright as well: almost everything that happens in a browser can be accelerated further and at some point we may find that the boundaries between CPU rendering and GPU rendering go away completely. Digging a little deeper into this topic and running your own experiments will make it easier for you to pick up new enhancements as they come down the pipe.
If you’d like a visualization of how the browser breaks up your page, Chrome has a useful visual debugging feature: enter “chrome://flags” and look for a setting called “Composited render layer borders.” This will put a red rectangle around each different surface that is being sent to the GPU.
There are many more useful details around, and I’ll likely update this post down the road. If you’re a browser implementor, please forgive my lightweight treatment and feel free to correct any details in the comments.