When you write HTML code, and the browser renders it, the process by which the HTML elements end up as a rendered web page on the screen can roughly be summarized as follows:
- Style & Layout: Apply the CSS style to the elements being rendered and generate the geometry and position of the elements.
- Paint: Fill out the pixels for the element into layers.
- Composite: Draw the layers onto the screen.
The first thing you should do when you start investigating sub-optimal animation performance is to use the profiling tools you have available with the Emulator. There are mainly 2 ways: using the control panel and using the Chromium Developer Tools (DevTools).
You have the following options available to you in the Control Panel:
- Show Paint Regions: Enables the rendering of paint regions in the Emulator. These green rectangles appear anywhere on the screen where painting is don. They start off strong and slowly fade away.
- Show Layer Borders: Enabling this option renders a transparent bronze-colored rectangle around DOM elements in your application which are considered a layer in the chromium rendering engine. These layers translate directly into hardware layers and so any animations on them can be performed entirely in hardware, without the need to trigger any repaints by the CPU.
- Show FPS Counter: This option enables a live FPS counter to appear on the top-right corner of the Emulator screen. It shows you the current frames-per-second (fps) count of your web app as well as a running after and a graph. You should try to keep the FPS count at at least 30 fps at all times, preferably 60 fps.
There are loads of articles available on the internet that detail how to use Devtools. You can get started at the official Chrome DevTools documentation.
Tips & Tricks
1. translateZ Layer Creation
As mentioned at the beginning of this document, rendering in Chromium is done in 3 stages namely Style/Layout, Painting and Compositing. You should always strive to trigger as few of these operations as possible. This means you should try to avoid Layout, and Painting as much as you can. One way of doing this is to promote animating elements into their own layer, by adding the
transform: translateZ(0); CSS property into your element. This enables Chromium to cache the layer and just recomposite it if there was only simple changes to the page, like transform or opacity updates that didn't trigger layout or paint of the whole page.
Ideally, this would mean one layer per animatable entity. However, creating too many layers has its own overhead so you need to come up with a good compromise. A good rule-of-thumb is to put together all elements that have the same transform into the same layer. If you start noticing your performance going down due to too many layers, you should start merging layers or shaving off animations until your layer count is under control. For more information, see GPU accelerate your DOM elements.
Note: There are more ways to promote an element into its own layer so don't go using the translateZ transform on every element. For more information on what causes layer creation, refer to the section below on Compositing Reasons.
2. translate3d Animation
Similar to the translateZ CSS transform property, we can use the
3. Use CSS Animations
4. Avoid realtime DOM generation
5. Avoid over-nesting DOM elements
An image-in-a-div is a good idea but an image-in-a-div-in-a-div-in-a-div-in-a-div-in-a-div might be overkill. A simple change in an element deep inside the nest might trigger unnecessary checking throughout the nest to see if reflow is needed, even if it isn't! For more information, see this Minimizing browser reflow article.
Opera TV Store Example
We look at a simple example here with an older version of the the Opera TV Store user interface. To see if it is well-designed and high performance, we turn on paint rects and layer borders turning on these options in the control panel. When the TV Store loads, this is the screen we see:
Figure 1: Analyzing the Opera TV Store.
Looking at the resulting image of the TV Store, you can observe the following:
- There is a blue grid diving the screen into squares. This represents the tiles being rendered for each layer and the default size is 256x256 pixels. You do not need to worry about this tiling when analyzing and debugging your animations.
- The page also has certain orange rectangles (4 in this case). Each rectangle represents a layer in the page. You can see we have a layer for the Opera TV Store logo, a layer for the main menu on top, a layer for the carousel of apps in the middle and a final layer for the background covering the entire screen.
- The number of layers is quite low and it is close to the optimal. Each layer is a separate animatable entity, i.e. all the elements within a layer are transformed together. This is because all the menu items are translated left or right at the same time and speed, as well as all the carousel items in the main body. The Opera TV Store logo does not animate or move, so we can live without having it as a layer. The background is always its own layer so we can't avoid that.
- We observe that the focus rectangle (blue above on the 'iG Arena' app) is not its own layer and we can immediately see that it will be a problem when it moves independently of the carousel items below it. In fact, we expect the focus rectangle to be repainted every time is it moved, so lets put this to the test. Below you have a screenshot of the focus rectangle animation. We see indeed a green rectangle overlay on both the old carousel item and the new one when the focus is moved.
- There is a good reason for this in the case of the TV Store portal. It is because the whole background color of the carousel item div changes when it is selected so the thumbnail has to repainted. And this brings us to an important conclusion to this section: optimization is always a compromise between performance and quality. You should save as much painting as you can afford, but not more. It is simply a mattering of finding your comfort spot on the performance/quality curve.
Figure 2: Repaint caused when the focus rectangle moves.
As promised, here is a (non-exhaustive) list of reasons why Chromium promotes an element to its own layer (taken directly from Gpu Accelerated Compositing in Chrome:
- Layer has 3D or perspective transform CSS properties.
- Layer is used by
<video>element using accelerated video decoding.
- Layer is used by a
<canvas>element with a 3D context or accelerated 2D context.
- Layer is used for a composited plugin.
- Layer uses a CSS animation for its opacity or uses an animated CSS transform.
- Layer uses accelerated CSS filters.
- Layer has a descendant that is a compositing layer.
- Layer has a sibling with a lower z-index which has a compositing layer (in other words the layer overlaps a composited layer and should be rendered on top of it).
The following links give more information about optimizing your web code:
- Reduce the size of the above-the-fold content
- Improving Rendering Performance with Chromium Dev Tools
- Profiling Long Paint Times with DevTools' Continuous Painting Mode
- High Performance Animations
- DevTools: Visually Re-engineering CSS For Faster Paint Times
- Jank Busting for Better Rendering Performance
- Jank-Free: In Pursuit of Smooth Web Apps
- Improving Web App Performance With the Chromium DevTools Timeline and Profiles