I am trying to understand, in practice, how the layout → paint → composite pipeline of Chrome works. During my testing, I got confused about Chrome's behavior in the following situation. (Codepen)
var button = document.querySelector('button');
var red = document.querySelector('.red');
var blue = document.querySelector('.blue');
button.addEventListener('click', function() {
red.classList.toggle('test');
})
.wrapper {
height: 100%;
width: 100%;
background-color: teal;
position: static;
}
.square {
height: 200px;
width: 200px;
position: static;
transition: transform 0.3s ease,
opacity 0.3s ease,
width 0.3s ease,
height 0.3s ease,
box-shadow 0.3s ease;
}
.red {
/* position: absolute; */
background-color: red;
top: 100px;
left: 100px;
/* will-change: transform; */
opacity: 1;
}
.blue {
background-color: blue;
z-index: 3;
}
.test {
/* transform: translate3d(50px, 50px, 0); */
/* opacity: 0; */
width: 60px;
height: 60px;
/* box-shadow: 0px 0px 0px 10px rgba(0,0,0,.5) */
}
button {
position: fixed;
right: 100px;
top: 50px;
z-index: 10000;
font-weight: bold;
background-color: yellow;
padding: 5px 10px;
/* will-change: transform; */
}
<div class="wrapper">
<div class="red square"></div>
<div class="blue square"></div>
</div>
<button>Click</button>
To reproduce.
position
of the red square too.We have two squares, a red one and a blue one, inside a wrapper element. In Chrome's Layers panel this whole thing shows as one layer.
If both squares are static
positioned, when I transition the width and height of the red one, I can see that the whole layer gets repainted, which, to me, makes sense, since, if all 3 elements are in the same layer, changing the dimensions of one, changes the whole layer's end result, so the whole layer has to be repainted.
If I set the red square to absolute
positioning (you can do that by uncommenting the line in the .red
style rules), when I transition its width and height, even though the dev tools still show one layer, only the red square, inside that layer, is shown as repainted.
Question.
The second scenario does not make sense to me.
If the two squares and the wrapper element are all in the same layer, shouldn't changing one element affect the whole layer and cause the layer as a whole to repaint, instead of just the red square?
Additional questions.
Does Chrome, during the layout phase (or whatever phase it is that determines the layers) separate some elements into their own layers (due to position
properties for example)? Is that why it is able to repaint them separately? Does it, after the compositing phase, dump them, so the developer only sees one layer in the dev tools?
Related background.
My rough understanding of the painting process of modern browsers is as follows:
will-change
for example).Chrome DevTools Paint flashingPress Command+Shift+P (Mac) or Control+Shift+P (Windows, Linux) to open the Command Menu. Start typing Rendering in the Command Menu and select Show Rendering. In the Rendering tab enable Paint flashing.
Open the Rendering tab and then enable Paint Flashing. With this option switched on Chrome will flash the screen green whenever painting happens. If you're seeing the whole screen flash green, or areas of the screen that you didn't think should be painted, then you should dig in a little further.
Fortunately, there is a tool that makes repaints spotting easier — Layers panel in Chrome Dev Tools. To reveal the panel you need to open a customisation menu in Chrome Dev Tools and in “More Tools” choose “Layers” option.
So, the confusing bit here was the fact that the Paint flashing of the dev tools, only flashes the part of the layer that gets invalidated from the previous frame (so if one absolute
positioned square starts getting smaller, the invalidated area between frames is an area with the dimensions and coordinates that the square had in the previous frame)
However, internally the whole layer gets repainted, no matter how big or small the invalidated parts are between frames.
So, for example, a blinking cursor will look small on Paint Flashing, but in reality the entire layer needs to be repainted.
Indeed, if we open up the Performance panel and enable the Advanced Painting Instrumentation option, we can see that between the square transitions the whole layer gets painted in both scenarios described in the question.
Sources
https://twitter.com/paul_irish/status/971196975013027840
https://twitter.com/paul_irish/status/971196996924030977
https://twitter.com/paul_irish/status/971197842713800704
Some observations
If we were to minimise the Layout and Painting steps to make as few operations as possible we should separate the red square and the yellow button to their own render layers.
This way interacting with the button and resizing the square will only affect their respective layers and will not cause a repaint to the background layer (which includes the background and the blue square).
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