xqfrms
首发于xqfrms
浏览器 渲染,绘制流程及性能优化

浏览器 渲染,绘制流程及性能优化

渲染性能

developers.google.com/w

像素渲染流水线

在编写web页面时,你需要理解你所写的页面代码是如何被转换成屏幕上显示的像素的。

这个转换过程可以归纳为这样的一个流水线,包含五个关键步骤

JavaScript(DOM) > Style > Layout > Paint > Composite

「布局」 === 重排」


浏览器执行的所有步骤:

  1. 处理 HTML 标记,构建 DOM 树
  2. 处理 CSS 标记,构建 CSSOM 树
  3. 将 DOM 树和 CSSOM 树融合成渲染树
  4. 根据渲染树来布局(重排),计算每个节点的几何信息。
  5. 在屏幕上绘制(重绘)各个节点。


JS DOM > 样式 CSSOM > 布局 > 绘制 > 合成



JavaScript。

一般来说,我们会使用JavaScript来实现一些视觉变化的效果。比如用jQuery的animate函数做一个动画、对一个数据集进行排序、或者往页面里添加一些DOM元素等。当然,除了JavaScript,还有其他一些常用方法也可以实现视觉变化效果,比如:CSS Animations, Transitions和Web Animation API。
计算样式。这个过程是根据CSS选择器,比如.headline或.nav > .nav_item,对每个DOM元素匹配对应的CSS样式。这一步结束之后,就确定了每个DOM元素上该应用什么CSS样式规则。


布局

上一步确定了每个DOM元素的样式规则,这一步就是具体计算每个DOM元素最终在屏幕上显示的大小和位置。web页面中元素的布局是相对的,因此一个元素的布局发生变化,会联动地引发其他元素的布局发生变化。比如,<body>元素的宽度的变化会影响其子元素的宽度,其子元素宽度的变化也会继续对其孙子元素产生影响。因此对于浏览器来说,布局过程是经常发生的。


绘制

绘制,本质上就是填充像素的过程。包括绘制文字、颜色、图像、边框和阴影等,也就是一个DOM元素所有的可视效果。一般来说,这个绘制过程是在多个层上完成的。


渲染层合并

由上一步可知,对页面中DOM元素的绘制是在多个层上进行的。在每个层上完成绘制过程之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。对于有位置重叠的元素的页面,这个过程尤其重要,因为一旦图层的合并顺序出错,将会导致元素显示异常。

上述过程的每一步中都有发生jank的可能,因此一定要弄清楚你的代码将会运行在哪一步。



rasterize 光栅化 & 光栅化 rasterization


Note: 你可能听说过 "rasterize" 这个术语,它通常被用在绘制过程中。
绘制过程本身包含两步: :1)创建一系列draw调用
;2)填充像素。 
第二步的过程被称作 "rasterization" 。
因此当你在DevTools中查看页面的paint记录时,你可以认为它已经包含了 rasterization。(有些浏览器会使用不同的线程来完成这两步,不过这也不是web开发者能控制的了)


虽然在理论上,页面的每一帧都是经过上述的流水线处理之后渲染出来的,但并不意味着页面每一帧的渲染都需要经过上述五个步骤的处理。


实际上,对视觉变化效果的一个帧的渲染,有这么三种 常用的 流水线:

1. JS / CSS > 计算样式 > 布局 > 绘制 > 渲染层合并

2. JS / CSS > 计算样式 > 绘制 > 渲染层合并

3. JS / CSS > 计算样式 > 渲染层合并


如果你修改一个非样式且非绘制的CSS属性,那么浏览器会在完成样式计算之后,跳过布局和绘制的过程,直接做渲染层合并。
第三种方式在性能上是最理想的,对于动画和滚动这种负荷很重的渲染,我们要争取使用第三种渲染流程。
Note: 如果你想对哪些属性会触发CSS Triggers和高性能动画方面了解更多,请参考:使用渲染层合并属性。


CSS Triggers

CSS Triggers
transformClose
Changing transform does not trigger any geometry changes or painting, which is very good. This means that the operation can likely be carried out by the compositor thread with the help of the GPU.
更改变换不会触发任何几何更改或绘画,这是非常好的。这意味着操作可能由合成器线程在GPU的帮助下执行。
CSS Triggers
Changing float alters the geometry of the element. That means that it may affect the position or size of other elements on the page, both of which require the browser to perform layout operations.
Once those layout operations have completed any damaged pixels will need to be painted and the page must then be composited together.
更改浮动将更改元素的几何。这意味着它可能会影响页面上其他元素的位置或大小,这两个元素都需要浏览器执行布局操作。
一旦这些布局操作完成,任何损坏的像素将需要被绘制,然后该页必须被合成在一起。


developers.google.com/w


性能优化是一门做减法的艺术。

我们首要要尽力简化页面渲染过程,然后要使渲染过程的每一步都尽量高效。在很多时候,我们需要跟浏览器一起努力来创建高性能web应用,而不是跟浏览器对着干。要记住,以上列举的流水线中的每一步,在时间消耗上是各不相同的,有些步骤是相对更费时的。
接下来,让我们深入到这个流水线中的每一步去看看。我们会以一些常见问题为例,阐述如何发现和分析这些问题,并尝试去解决它们。


浏览器渲染优化
想深入了解渲染性能吗?快看看这堂课程吧 它能帮助你了解浏览器是如何把HTML/CSS/JavaScript代码转换成屏幕上你看到的一个个像素的 如何使用DevTools来测量页面性能、以及如何优化你的页面渲染速度。

浏览器渲染优化





重新渲染,就需要重新生成布局和重新绘制。前者叫做"重排"(reflow),后者叫做"重绘"(repaint)。

重新渲染,就需要重新生成布局和重新绘制。前者叫做"重排"(reflow),后者叫做"重绘"(repaint)。

需要注意的是,"重绘"不一定需要"重排",比如改变某个网页元素的颜色,就只会触发"重绘",不会触发"重排",因为布局没有改变。但是,"重排"必然导致"重绘",比如改变一个网页元素的位置,就会同时触发"重排"和"重绘",因为布局改变了。

网页性能管理详解 - 阮一峰的网络日志




浏览器的重绘(repaints)与重排(reflows)

在项目的交互或视觉评审中,前端同学常常会对一些交互效果质疑,提出这样做不好那样做不好。主要原因是这些效果通常会产生一系列的浏览器重绘和重排,需要 付出高昂的性能代价。

那么,什么是浏览器的重绘和重排呢?

二者何时发生以及如何权衡?

如何在具体的开发过程中将重绘和重排引发的性能问题考虑进去?

本文期 待可以部分解释以上三个问题。

浏览器从下载文档显示页面的过程是个复杂的过程,这里包含了重绘和重排。

各家浏览器引擎的工作原理略有差 别,但也有一定规则。

简单讲,通常在文档初次加载时,浏览器引擎会解析HTML文档来构建DOM树,之后根据DOM元素的几何属性 CSSOM 树构建一棵渲染树

渲染树的每个节点都有大小和边距等属性,类似于盒子模型(由于隐藏元素不需要显示,渲染树中并不包含DOM树中隐藏的元素)。

当渲染树构建完成后,浏览器 就可以将元素放置到正确的位置了(Layout 布局),再根据渲染树节点的样式属性绘制出页面。

由于浏览器的流布局,对渲染树的计算通常只需要遍历一次就可以完成。

但 table及其内部元素除外,它可能需要多次计算才能确定好其在渲染树中节点的属性,通常要花3倍于同等元素的时间。这也是为什么我们要避免使用 table做布局的一个原因。

重绘是一个元素外观改变所触发的浏览器行为,例如改变vidibility、outline、背景色等属性。浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。

重绘不会带来重新布局,并不一定伴随重排。???

重排是更明显的一种改变,可以理解为渲染树需要重新计算。下面是常见的触发重排的操作:

1. DOM元素的几何属性变化

当DOM元素的几何属性变化时,渲染树中的相关节点就会失效,浏览器会根据DOM元素的变化重建构建渲染树中失效的节点。

之后,会根据新的渲染树重新绘 制这部分页面。而且,当前元素的重排也许会带来相关元素的重排。

例如,容器节点的渲染树改变时,会触发子节点的重新计算,也会触发其后续兄弟节点的重排, 祖先节点需要重新计算子节点的尺寸也会产生重排。最后,每个元素都将发生重绘。

可见,重排一定会引起浏览器的重绘,一个元素的重排通常会带来一系列的反 应,甚至触发整个文档的重排和重绘,性能代价是高昂的。

2.DOM树的结构变化

DOM树的 结构变化时,例如节点的增减、移动等,也会触发重排。

浏览器引擎布局的过程,类似于树的前序遍历,是一个从上到下从左到右的过程。通常在这个过程中,当前 元素不会再影响其前面已经遍历过的元素。

所以,如果在body最前插(::before)入一个元素,会导致整个文档的重新渲染,而在其后插(::after 伪元素)入一个元素,则不会影响到前面的 元素

3.获取某些属性

浏览器引擎可能会针对重排做了优化。

比如Opera,它会等到有足够 数量的变化发生,或者等到一定的时间,或者等一个线程结束,再一起处理,这样就只发生一次重排。

但除了渲染树的直接变化,当获取一些属性时,浏览器为取得 正确的值也会触发重排。这样就使得浏览器的优化失效了。

这些属性包括:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、 clientTop、clientLeft、clientWidth、clientHeight、getComputedStyle() (currentStyle in IE)。

所以,在多次使用这些值时应进行缓存

此外,改变元素的一些样式,调整浏览器窗口大小等等也都将触发重排。

开发中,比较好的实践是尽量减少重绘次数缩小重排的影响范围。例如:


1. 将多次改变样式属性的操作合并成一次操作。例如,

DOM style 操作 to CSS class 操作


JS:

var changeDiv = document.getElementById('changeDiv'); 
changeDiv.style.color = '#093'; 
changeDiv.style.background = '#eee'; 
changeDiv.style.height = '200px';

可以合并为:

CSS:

div.changeDiv {
background: #eee;
color: #093;
height: 200px;
}

JS:

document.getElementById('changeDiv').className = 'changeDiv';

2. 将需要多次重排(布局)的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位

3. 在内存中多次操作节点,完成后再添加到文档中去。

虚拟DOM) React

例如要异步获取表格数据,渲染到页面。

可以先取得数据后在内存中构建整个表格的变量html片段缓存,再一次性添加到文档中去,而不是循环添加每一行。

内存中循环

4. 由于display属性为none的元素不在渲染树中,对隐藏的元素操作不会引发其他元素的重排。

如果要对一个元素进行复杂的操作时,可以先隐藏它,操作完成后再显示。这样只在隐藏和显示时触发2次重排。

5. 在需要经常取那些引起浏览器重排的属性值时,要缓存到变量。

在最近几次面试中比较常问的一个问题:在前端如何实现一个表格的排序

如果应聘者的方案中考虑到了如何减少重绘和重排的影响,将是使人满意的方案。

编辑于 2017-02-17

文章被以下专栏收录