Aside from looking great and loading quickly, the perfect website should also feel smooth. The act of scrolling down the page should be more luxury sedan and less go-kart. Animations should be like butter and silk having a night out together. While the average visitor might not consciously notice smooth rendering performance, they most certainly notice its absence. In this post, I want to discuss one of the more surprising culprits of website choppiness (also known as “jank”) and how to avoid it whenever possible. When a page feels choppy, many developers assume the page is simply overloaded with content or burdened with a bit of clumsy JavaScript. You might be surprised to learn that in many cases your CSS is actually what’s preventing you from reaching 60 frames per second. The key to writing silky CSS is to avoid forcing the web browser to perform unnecessary repaints and reflows. For the time being, meditate on how important it is for visitors to associate your brand with luxury instead of jank. Let’s Pretend Web Browsers Are Lazy If we want a website to scroll and display animations (and transitions) at 60 frames per second, we are essentially giving the browser a new deadline every 16.67 milliseconds. If we give the browser a task that is too complex to complete in 16.67 milliseconds, jank will enter our lives. Our goal is to treat the browser as if it is terribly lazy, so let’s spend a few minutes to learn about what the browser considers work and what it considers a stroll in the park. The Browser’s Rendering Steps The browser has four steps to follow whenever it needs to update what it displays on the screen. The only time the browser absolutely needs to perform all four steps is when the page is first loaded. Beyond that, it’s up to our CSS and JS to determine how lazy the browser is allowed to be. Let’s review the steps: (Re)Calculate: In this step the browser determines which CSS rules apply to which elements. Layout: Here, the browser generates the dimensions and position for each element on the page. Paint: This is when the browser fills in the pixels for each element into layers but doesn’t actually draw those layers to the screen yet. Composite Layers: In this final step, the browser draws the pre-painted layers to the screen. These four steps build upon each other, which means the higher up the chain our CSS reaches, the more dominoes we are knocking down for the browser to pick up again . . . every 16.67 milliseconds. We ideally want our CSS to only touch the final step, as this allows for maximum browser laziness (i.e. silky, jank-free performance). Earlier, I mentioned we wanted to avoid unnecessary repaints and reflows, but what do these terms actually mean? Repaints Imagine we write a bit of CSS that transitions the background-color of a button from yellow to orange on hover. The good news is that this won’t trigger any layout change, so the browser can skip straight to step #3 and only repaint and redraw the button. The bad news is that repainting is typically an expensive operation for the browser to perform and should be avoided if possible. We don’t need to abandon all CSS effects in an effort to reduce repaints. We’ll learn in a moment that a bit of creativity can eliminate a lot of jank. Reflows Imagine we write a bit of CSS that changes the “height” of an element on hover. Not only will the browser need to adjust the size of the hovered element, but it also needs to regenerate the layout of all elements that come after it on the page and finally repaint and redraw all of this affected content. This is known as a reflow, and it creates a lot of work for the browser. While it’s difficult to avoid reflows entirely, keeping them to a minimum is paramount in creating jank-free pages. So, What’s Left In CSS? There actually aren’t very many CSS properties we can transition or animate cheaply enough in the browser to reliably hit 60 frames per second. As of this writing, the main tools in our toolbox are translate, scale and opacity. These properties do not trigger reflows or repaints but instead directly manipulate the composite layers in the browser’s final rendering step. Position If you want to smoothly transition an element 200 pixels to the left on hover or tap, you might write “left: 200px” and call it a day. However, to reduce repaints and eliminate reflows, instead try “transform: translateX(200px)” – this lets the browser jump straight to the final rendering step and simply adjust the pre-painted layer. Size If you want an element’s transition in size (let’s imagine a double increase) to be silky smooth, try using “transform: scale(2)” instead of altering the “width” and “height” properties. Color Let’s jump back to our earlier example of wanting a button to transition its background color from yellow to orange on hover. While “background-color” forces repaints the “opacity” property does not. We can fade between two colors by simply transitioning one element’s opacity to smoothly reveal the underlying color of a second element. View an example of this technique on CodePen. Other Common Pitfalls Background images can be a significant source of jank if we are not careful. For example, if we want a background image to appear “fixed” (it stays in place as the rest of the page scrolls), we should avoid the “background-attachment: fixed” declaration as it typically forces a repaint on every frame while the user scrolls. To avoid these costly repaints, apply the background image to an element without specifying a background-attachment, make sure this element is behind any actual content (z-index) and then apply “position: fixed” to said element. Similarly, let’s imagine you want a background-image to zoom in a bit on hover or tap. Instead of transitioning the “background-size” property which forces repaints, create an extra element to assign the background image to and transition this element’s “transform: scale(1.2)” declaration and use “overflow: hidden” on your original element. View an example of this technique on CodePen. Becoming a CSS Jank Detective If you want to learn more about which effects on your pages are creating choppiness, I recommend spending some quality time with Chrome’s Developer Tools. In particular, make sure that your drawer is visible and then navigate to the “Rendering” tab and check the “Show paint rectangles” box.Now, as you scroll through websites and view transitions and animations, Chrome will highlight specific elements on the page with green rectangles when they are being repainted. If your website feels choppy, you will want to get creative and experiment with your CSS to reduce how frequently you see these green rectangles. Resources A particularly helpful resource for writing silky CSS is CSS Triggers, which lets you know which CSS properties trigger particular rendering steps in a browser. Reinventing a Jank-Free Wheel Oddly enough, the simplest CSS to create a style or effect is not necessarily the most optimized in terms of extracting frames per second. With the emergence of CSS3 and the ability to directly tap into the power of a device’s GPU, we have an exciting opportunity to create effects that are smoother than ever before. This means that we might need to roll up our sleeves, rethink new CSS solutions for common design patterns and have a bit of fun along the way. Now, go forth and create jank-free websites!

Development

Silky CSS: Minimizing Repaints & Jank


July 1, 2015

Aside from looking great and loading quickly, the perfect website should also feel smooth. The act of scrolling down the page should be more luxury sedan and less go-kart. Animations should be like butter and silk having a night out together. While the average visitor might not consciously notice smooth rendering performance, they most certainly notice its absence. In this post, I want to discuss one of the more surprising culprits of website choppiness (also known as “jank”) and how to avoid it whenever possible.

When a page feels choppy, many developers assume the page is simply overloaded with content or burdened with a bit of clumsy JavaScript. You might be surprised to learn that in many cases your CSS is actually what’s preventing you from reaching 60 frames per second. The key to writing silky CSS is to avoid forcing the web browser to perform unnecessary repaints and reflows. For the time being, meditate on how important it is for visitors to associate your brand with luxury instead of jank.

Let’s Pretend Web Browsers Are Lazy

If we want a website to scroll and display animations (and transitions) at 60 frames per second, we are essentially giving the browser a new deadline every 16.67 milliseconds. If we give the browser a task that is too complex to complete in 16.67 milliseconds, jank will enter our lives. Our goal is to treat the browser as if it is terribly lazy, so let’s spend a few minutes to learn about what the browser considers work and what it considers a stroll in the park.

The Browser’s Rendering Steps

The browser has four steps to follow whenever it needs to update what it displays on the screen. The only time the browser absolutely needs to perform all four steps is when the page is first loaded. Beyond that, it’s up to our CSS and JS to determine how lazy the browser is allowed to be. Let’s review the steps:

  1. (Re)Calculate: In this step the browser determines which CSS rules apply to which elements.
  2. Layout: Here, the browser generates the dimensions and position for each element on the page.
  3. Paint: This is when the browser fills in the pixels for each element into layers but doesn’t actually draw those layers to the screen yet.
  4. Composite Layers: In this final step, the browser draws the pre-painted layers to the screen.

These four steps build upon each other, which means the higher up the chain our CSS reaches, the more dominoes we are knocking down for the browser to pick up again . . . every 16.67 milliseconds. We ideally want our CSS to only touch the final step, as this allows for maximum browser laziness (i.e. silky, jank-free performance).

Earlier, I mentioned we wanted to avoid unnecessary repaints and reflows, but what do these terms actually mean?

Repaints

Imagine we write a bit of CSS that transitions the background-color of a button from yellow to orange on hover. The good news is that this won’t trigger any layout change, so the browser can skip straight to step #3 and only repaint and redraw the button. The bad news is that repainting is typically an expensive operation for the browser to perform and should be avoided if possible. We don’t need to abandon all CSS effects in an effort to reduce repaints. We’ll learn in a moment that a bit of creativity can eliminate a lot of jank.

Reflows

Imagine we write a bit of CSS that changes the “height” of an element on hover. Not only will the browser need to adjust the size of the hovered element, but it also needs to regenerate the layout of all elements that come after it on the page and finally repaint and redraw all of this affected content. This is known as a reflow, and it creates a lot of work for the browser. While it’s difficult to avoid reflows entirely, keeping them to a minimum is paramount in creating jank-free pages.

So, What’s Left In CSS?

There actually aren’t very many CSS properties we can transition or animate cheaply enough in the browser to reliably hit 60 frames per second. As of this writing, the main tools in our toolbox are translate, scale and opacity. These properties do not trigger reflows or repaints but instead directly manipulate the composite layers in the browser’s final rendering step.

Position

If you want to smoothly transition an element 200 pixels to the left on hover or tap, you might write “left: 200px” and call it a day. However, to reduce repaints and eliminate reflows, instead try “transform: translateX(200px)” – this lets the browser jump straight to the final rendering step and simply adjust the pre-painted layer.

Size

If you want an element’s transition in size (let’s imagine a double increase) to be silky smooth, try using “transform: scale(2)” instead of altering the “width” and “height” properties.

Color

Let’s jump back to our earlier example of wanting a button to transition its background color from yellow to orange on hover. While “background-color” forces repaints the “opacity” property does not. We can fade between two colors by simply transitioning one element’s opacity to smoothly reveal the underlying color of a second element. View an example of this technique on CodePen.

Other Common Pitfalls

Background images can be a significant source of jank if we are not careful. For example, if we want a background image to appear “fixed” (it stays in place as the rest of the page scrolls), we should avoid the “background-attachment: fixed” declaration as it typically forces a repaint on every frame while the user scrolls. To avoid these costly repaints, apply the background image to an element without specifying a background-attachment, make sure this element is behind any actual content (z-index) and then apply “position: fixed” to said element.

Similarly, let’s imagine you want a background-image to zoom in a bit on hover or tap. Instead of transitioning the “background-size” property which forces repaints, create an extra element to assign the background image to and transition this element’s “transform: scale(1.2)” declaration and use “overflow: hidden” on your original element. View an example of this technique on CodePen.

Becoming a CSS Jank Detective

If you want to learn more about which effects on your pages are creating choppiness, I recommend spending some quality time with Chrome’s Developer Tools. In particular, make sure that your drawer is visible and then navigate to the “Rendering” tab and check the “Show paint rectangles” box.

Now, as you scroll through websites and view transitions and animations, Chrome will highlight specific elements on the page with green rectangles when they are being repainted. If your website feels choppy, you will want to get creative and experiment with your CSS to reduce how frequently you see these green rectangles.

Resources

A particularly helpful resource for writing silky CSS is CSS Triggers, which lets you know which CSS properties trigger particular rendering steps in a browser.

Reinventing a Jank-Free Wheel

Oddly enough, the simplest CSS to create a style or effect is not necessarily the most optimized in terms of extracting frames per second. With the emergence of CSS3 and the ability to directly tap into the power of a device’s GPU, we have an exciting opportunity to create effects that are smoother than ever before. This means that we might need to roll up our sleeves, rethink new CSS solutions for common design patterns and have a bit of fun along the way. Now, go forth and create jank-free websites!