Tuning Mobile Swipe to 60FPS: Part 2

Several months ago, the Mobile Web team at Zoosk did a series of swipe optimization in our search slideshow. Some of the changes we made are discussed in another blog post by Robin Keller. Here I’d like to talk about another major optimization we made. That is to eliminate unnecessary repaints during the swipe animation by forcing layer creation for the slideshow element.

Layer and Hardware Acceleration

A layer is a structure used by the browser to render page elements.  After calculating the styles and breaking the DOM elements into layers, the browser would paint the layers into bitmaps and upload them to GPU as textures. For a very simple web page, there might be only one layer, but usually you would see multiple layers in a page. If you are not familiar with layer, I encourage you to read this introduction by Tom Wiltzius.

Layer-based rendering model is essential for hardware acceleration, which basically means having GPU assist the browser to do some heavy-lifting when rendering a page. When an operation that can be hardware accelerated is performed, the DOM element is ‘moved to its own layer’, and the operation is executed on the layer directly by GPU instead of the browser’s software-based rendering engine. That usually means better rendering performance, especially on mobile devices where the CPU is less powerful than the desktop computers.

CSS animation or transform are not hardware accelerated by default. So normally when you animate on CSS properties, it would cause the browser to repaint the element in every frame. There are, however, a few CSS properties that would trigger hardware acceleration. For example, opacity and 3D transform. Animating on these properties would trigger layer creation, and therefore hardware-accelerate the animation.

Unnecessary repaints in swipe animation

In our original implementation, when you swipe or drag the slideshow, the swipe animation is applied by setting a 2D transform property on the slideshow element:

screenshot_old

The following is the JS code that applies the transform:

js_old

Since we only need to move the slideshow horizontally, using translateX seems to be a reasonable choice. Although 3D transforms trigger layer creation, 2D transforms don’t. That means with our original implementation, browser would execute the animation with software rendering and that is why constant repaints occurred.

Let’s take a look at the timeline in Chrome DevTool when we drag the slideshow:

timeline_old

In the timeline, each green bar represents the painting tasks performed by the browser in each frame. Higher bar means longer paint time, and our goal is to keep the green bars as low as possible. As you can see, the browser has to repaint the slideshow element after every mousemove event in the timeline which is not ideal.

Forced layer creation

As mentioned earlier, 3D transforms trigger hardware acceleration. Therefore, our solution to the unnecessary repaints is to replace translateX with translate3d so that a layer will be created for the slideshow element:

js_new

From the screenshot below you can see that translate3d is applied to the slideshow element when we swipe it:

screenshot_new

Running Chrome DevTool again, here is the timeline we got:

timeline_new

The green bars are much lower than what we saw earlier. This is because the swipe animation is now hardware accelerated and no longer causes unnecessary repaints.

Conclusion

Fast animation is essential for great web experience. It is important that we avoid a repaint if possible because painting is an expensive process as mentioned earlier. Forcing layer creation is a useful technique to help us do so, but it’s not free. Developers should be aware of the trade-off and apply it wisely.