Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does transitioning the box shadow cause a full page repaint?

I noticed that my page was lagging when I hovered over an element with an animated box-shadow. Using Chrome's Devtools, I noticed that the entire page was being repainted when I hovered over the element. The repaint was taking 40+ milliseconds, or about 3 frames. The transition lasts about half a second, so there's noticeable lag during the half second.

How do I limit the repaint to just the area with the box shadow?

Here's a demo: http://jsfiddle.net/8sa41xfL/

html,body{
    height:100%;
}

#test{
    background:red;
    height:100px;
    width:200px;
    transition:box-shadow 0.5s;
}

#test:hover{
    box-shadow:0 0 3px 3px rgba(0,0,0,0.3);
}
<div id=test></div>

transform:translateZ(0) doesn't work on my page, but it works in the fiddle. Is there another fix aside from transform:translateZ(0)?

like image 359
Leo Jiang Avatar asked Aug 01 '15 00:08

Leo Jiang


People also ask

Can you transition box shadow?

You can add transitions to box shadows so that interactions with them will be smooth. For example, you can add a hover effect to an element that will make the box shadow's color grow darker. Here are the steps: Add a box shadow to an element and style with black color and 20% opacity.

How does box shadow work?

The box-shadow CSS property adds shadow effects around an element's frame. You can set multiple effects separated by commas. A box shadow is described by X and Y offsets relative to the element, blur and spread radius, and color.

What does box shadow do in CSS?

Used in casting shadows off block-level elements (like divs). The horizontal offset of the shadow, positive means the shadow will be on the right of the box, a negative offset will put the shadow on the left of the box.


2 Answers

As mentioned in the thread linked in Pierre's answer box-shadow are expensive to paint. Explaining why it is expensive would require in-depth understanding of the way rendering works and I don't have near enough knowledge to explain it completely. But this answer attempts to explain why the whole page gets repainted and the various possible methods to avoid it.


According to CSS Triggers website:

Changing box-shadow does not trigger any geometry changes, which is good. But since it is a visual property, it will cause painting to occur. Painting is typically a super expensive operation, so you should be cautious.

Once any pixels have been painted the page will be composited together.


Why does the whole page get repainted everytime?

The below articles explain the way that painting actually works at a high level:

  • HTML5 Rocks - How Browsers Work - Painting
  • The Chromium Project - GPU accelerated rendering in Chrome

Based on those articles, we can see that each node in the DOM tree that produces a visual output is considered as a RenderObject and that each RenderObject is part of a RenderLayer directly or indirectly. Whenever a change happens, the renderer (or the render object) invalidates its rectangle (or RenderLayer) on screen and triggers a repaint.

In this case it seems like the whole page is getting repainted because the #test element does not warrant the creation of a separate RenderLayer (based on the criteria mentioned in the Chromium Project article) and so becomes a part of the root render layer. Because it is a part of the root render layer the whole page is getting repainted everytime a repaint is required.

The following snippet proves that the above assertion is correct. Here, I have added a #cover element (with positioning) to enclose the #test element. Now since the #cover element has explicit positioning, it creates an extra layer above root layer and #test becomes a part of this intermediate layer. Now, we can see that the box-shadow transition repaints only this intermediate layer and not the whole page.

html,
body {
  height: 100%;
}
#cover {
  position: relative;
}
#test {
  background: red;
  height: 100px;
  width: 200px;
  transition: box-shadow 0.5s;
}
#test:hover {
  box-shadow: 0 0 3px 3px rgba(0, 0, 0, 0.3);
}
<div id=cover>
  <div id=test></div>
</div>

What is the solution?

There are various CSS properties that can be used to address this problem but they all seem to point to the same point at a high level - which is, to create a separate render layer for the #test element.

Below are a few possible options to create a separate render layer for the #test element:

  • By adding explicit position properties - This is the same option described in Pierre's answer but absolute positioning is not the only option. Even relative positioning would solve it.

    html,
    body {
      height: 100%;
    }
    #test {
      position: relative;
      background: red;
      height: 100px;
      width: 200px;
      transition: box-shadow 0.5s;
    }
    #test:hover {
      box-shadow: 0 0 3px 3px rgba(0, 0, 0, 0.3);
    }
    <div id=test></div>
  • By adding transparency (opacity) - Browsers seem to treat even opacity: 0.99 as adding transparency and it is very useful because adding this doesn't cause any visual difference.

    html,
    body {
      height: 100%;
    }
    #test {
      background: red;
      height: 100px;
      width: 200px;
      opacity: 0.99;
      transition: box-shadow 0.5s;
    }
    #test:hover {
      box-shadow: 0 0 3px 3px rgba(0, 0, 0, 0.3);
    }
    <div id=test></div>
  • By adding a dummy CSS filter - We could add a filter: blur(0px) as it would do nothing.

    html,
    body {
      height: 100%;
    }
    #test {
      background: red;
      height: 100px;
      width: 200px;
      -webkit-filter: blur(0px);
      filter: blur(0px);
      transition: box-shadow 0.5s;
    }
    #test:hover {
      box-shadow: 0 0 3px 3px rgba(0, 0, 0, 0.3);
    }
    <div id=test></div>
like image 196
Harry Avatar answered Oct 06 '22 00:10

Harry


CSS box shadows are expensive to paint. Read more on SO here.

If you want to avoid a full page repaint, use a position:absolute on your element. This will repaint the area that surrounds your element without affecting the whole page. Fiddle.

like image 41
Pierre Avatar answered Oct 06 '22 01:10

Pierre