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)
?
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.
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.
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.
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:
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>
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With