为什么Chrome在更改时不需要重新绘制整个层?

18

我正在尝试实际了解Chrome的布局→绘制→合成管道是如何工作的。 在我的测试中,我对以下情况下Chrome的行为感到困惑。(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>

重现步骤

  • 使用Chrome浏览器;
  • 在开发者工具中开启高亮重绘
  • 打开图层面板;
  • 点击黄色按钮,切换红色正方形的大小;
  • 查看被标记为“已重绘”的内容以及当前的图层;
  • 尝试在改变红色正方形position之后执行上述操作。

我们有一个包含红色正方形和蓝色正方形的容器元素。在Chrome的图层面板中,整个容器显示为一个图层。

  • 如果两个正方形都是static定位,当我转换红色正方形的宽度和高度时,我可以看到整个图层被重新绘制,这很合理,因为如果所有三个元素都在同一图层中,更改一个元素的尺寸将导致整个图层的最终结果改变,所以整个图层必须被重新绘制。

  • 如果我将红色正方形设置为absolute定位(您可以取消注释.red样式规则中的行),当我转换其宽度和高度时,尽管开发者工具仍然显示为一个图层,但只有在该图层内的红色正方形被标记为已重绘。

问题
第二种情况对我来说没有意义。

如果两个正方形和包装元素都在同一图层中,更改一个元素不应影响整个图层并导致整个图层重新绘制,而不是只有红色正方形?

补充问题
Chrome浏览器在布局阶段(或确定图层的任何阶段)是否会将某些元素分成单独的图层(例如由于position属性)?这就是它能够分别重新绘制它们的原因吗?在合成阶段之后,它是否会将它们合并,以便开发人员只看到一个图层?

相关背景
我大致了解现代浏览器的绘制过程如下:

  • 在布局阶段,浏览器将CSS与HTML信息结合起来,以确定屏幕上可视元素的位置和尺寸。我还认为这是它确定有多少个渲染图层的时间(例如由于will-change)。
  • 在绘制阶段,浏览器为每个图层制作一个帧缓冲区,其中包含基于布局数据的每个像素应显示的颜色信息。
  • 在合成阶段,GPU根据绘制阶段生成的数据将所有图层组合在一起,并将最终结果发送到屏幕上。


1
你需要在问题描述中发布一个完整但最小化的例子,包括标记或代码,以允许我们重现该问题,而不是使用Codepen或其他第三方网站。[MCVE] - Rob
2
你能稍微澄清一下你的问题吗?你是在问整个过程是如何工作的吗?(需要几本书来解释所有内容),还是你只是关心你的 position: absolute 情况?(如果是这样,简单地在网上搜索就足够了:https://www.w3.org/TR/CSS2/visuren.html#absolute-positioning) - Kaiido
1
@Kaiido 我尝试澄清一下。是的,我想问的是整个过程,position: absolute 只是一个例子。我的问题实质是为什么我只看到整个层中的一个元素被重新绘制,而不是整个层。也许我表达得不对,我不知道。 - Dimitris Karagiannis
3
OP 在 meta 上询问了这个问题。(/cc @Rob) - Just a student
1个回答

17

因此,这里令人困惑的是 开发者工具的 Paint flashing ,它只会闪烁前一帧失效的那部分图层(例如,如果一个绝对定位的正方形开始变小,则帧与帧之间失效的区域就是前一帧中该正方形的尺寸和坐标)。

然而,在内部,整个图层都会被重新绘制,无论帧之间失效的部分大小如何。

因此,例如,一个闪烁的光标在 Paint Flashing 中看起来很小,但实际上需要重新绘制整个图层。

实际上,如果我们打开 Performance 面板并启用 Advanced Painting Instrumentation 选项,我们可以看到在正方形过渡期间,在问题描述的两种情况下都会重新绘制整个图层

Chrome Paint analysis 1

Chrome Paint analysis 2

参考来源

https://twitter.com/paul_irish/status/971196975013027840
https://twitter.com/paul_irish/status/971196996924030977
https://twitter.com/paul_irish/status/971197842713800704

一些观察

如果我们想要尽可能减少布局和绘制的步骤,我们应该将红色正方形和黄色按钮分别放在自己的渲染层中。
这样,与按钮进行交互和调整正方形大小,只会影响它们各自的层,并不会导致重新绘制背景层(包括背景和蓝色正方形)。

感谢您在这里发布Chrome开发人员的答案,但请下次要求他们在StackOverflow上回复。我们尽可能减少那些真正了解情况的人给出的非正式回答,这样整个社区才能从中受益! - NH.
6
我已经联系了他。由于没有任何保证他会亲自发布答案,暂时就只能这样了。 - Dimitris Karagiannis
@DimitrisKaragiannis 这是否意味着当页面中的所有元素都在一个根层上时,每当该层上的任何视觉变化发生时,所有这些元素都会被绘制?比如说按钮悬停时的背景颜色?这听起来对我来说相当疯狂。 - Jan Krakora
@behnil 没错!你可以打开开发者工具试一下,亲眼看看。但对于简单的页面来说,这应该不是问题,浏览器很快。但如果你开始注意到卡顿,那么你肯定应该尝试进行优化。 - Dimitris Karagiannis
@DimitrisKaragiannis 我仍然无法相信整个页面(在这种情况下是层)需要重新绘制。请查看此文章,特别是关于矩形失效的部分。https://hacks.mozilla.org/2017/10/the-whole-web-at-maximum-fps-how-webrender-gets-rid-of-jank - Jan Krakora
@behnil 嗯,信不信由你,这就是发生的事情,你链接的文章甚至提到了图层以及如何使用图层进行重绘和合成。打开你的开发工具并检查一下问题的演示页面吧 :) - Dimitris Karagiannis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接