18

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.

  • Use Chrome;
  • turn Highlight repaints on, in the dev tools;
  • open the Layers panel;
  • click the yellow button to toggle the size of the red square;
  • see what is highlighted as "repainted", as well as the current layers;
  • try doing it after changing the 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:

  • During the layout phase, the browser combines CSS with HTML information in order to figure out positions and dimensions of visual elements on screen. I also believe this is the time where it determines how many render layers are there (due to will-change for example).
  • During the paint phase, the browser makes a framebuffer for each layer, containing information about what color each pixel should display, based on the layout data.
  • During the compositing phase, the GPU composites all the layers together, based on the data generated during the paint phase, and sends the final result to the screen.
TylerH
  • 20,799
  • 66
  • 75
  • 101
Dimitris Karagiannis
  • 8,942
  • 8
  • 38
  • 63
  • 1
    You are required to post a complete but minimal example of your problem markup or code here within your question that allows us to duplicate the issue, not a Codepen or any other third party site,: [mcve] – Rob Mar 05 '18 at 00:33
  • 2
    Could you clarify a bit your question? Are you asking how the whole process works? (would take a few entire books to explain it all), or are you really just concerned by your `position: absolute` case? (If so, a simple search on the web is enough: https://www.w3.org/TR/CSS2/visuren.html#absolute-positioning) – Kaiido Mar 05 '18 at 01:14
  • 1
    @Kaiido I tried clarifying it a little. Yeah, I guess I am asking about the whole process, the `position: absolute` was just an example. The essence of my question is why I only see one element from the whole layer getting repainted instead of the whole layer. Maybe I am asking it wrong, I don't know. – Dimitris Karagiannis Mar 05 '18 at 01:39
  • 3
    OP has asked about this question [on meta](https://meta.stackoverflow.com/q/364248). (/cc @Rob) – Just a student Mar 07 '18 at 11:37

1 Answers1

17

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.

Chrome Paint analysis 1

Chrome Paint analysis 2

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).

halfer
  • 19,824
  • 17
  • 99
  • 186
Dimitris Karagiannis
  • 8,942
  • 8
  • 38
  • 63
  • Thanks for posting the Chrome dev's answer here, but please ask them to reply in StackOverflow next time. The less side-channel answers we have from the people who are really in the know, the more the whole community can benefit! – NH. Mar 07 '18 at 20:48
  • 6
    I have already contacted him. Since there are no guarantees he will actually post an answer himself this will have to do for now. – Dimitris Karagiannis Mar 07 '18 at 20:56
  • @DimitrisKaragiannis Does that mean that when all elements within a page are in one root layer, all that elements are drawn every time something visually change on that layer? Let's say background color on a button on hover? That sounds pretty crazy to me. – Jan Krakora Aug 29 '18 at 21:10
  • @behnil that's correct! You can open the devtools to try it out and see for yourself. But for simple pages it should be no problem, browsers are fast. You should definitely try to optimize if you start noticing stutter though. – Dimitris Karagiannis Aug 29 '18 at 21:21
  • @DimitrisKaragiannis I still can't believe the whole page (layer in this case) need to be redrawn. Check this article, especially the part about rectangle invalidation. https://hacks.mozilla.org/2017/10/the-whole-web-at-maximum-fps-how-webrender-gets-rid-of-jank – Jan Krakora Aug 30 '18 at 12:19
  • @behnil well, believe it or not this is what happens, the article you linked even makes mention of layers and how repainting and compositing with layers work. Do open up your Dev tools and check on the demo page from the question :) – Dimitris Karagiannis Aug 30 '18 at 12:50