前言
上次面试中,面试小姐姐问到了我关于重绘和重排的问题,不出意外,重绘是...重排是...
小姐姐:你知道重绘和重排吗?
我:...
错误示范:
重排就是当页面的结构发生变化了,就会重排,比如改变变字体的大小,增删 DOM 元素这样的。重绘就是页面结构没有变化,只是外观变了,比如改了一下字体颜色、背景颜色这样的。就只会发生重绘。
当你提到以上答案,面试官可能会继续引导:那重排和重绘有什么关系吗?
此时你可能会说:重排一定会导致重绘,重绘不一定会导致重排。因为重排结构发生变化了嘛,肯定会导致重绘。
那么此时面试官可能对这道题已经不抱太大希望了,或许会继续引导你,是否知道关键渲染路径
考点
事实上,这道题目一般考察两个点:
- 浏览器的关键渲染路径
- 性能的优化,如何减少重绘和重排,能答出这个,需要你对浏览器的关键路径渲染有一定的理解
这里需要我们理解,我们的html、css、js代码是如何被浏览器渲染到页面上成为一个个像素点的,涉及到CRP概念(关键渲染路径)
关键渲染路径
关键渲染路径(Critical Rendering Path)
是浏览器将 HTML,CSS 和 JavaScript 转换为屏幕上的像素所经历的步骤序列。优化关键渲染路径可提高渲染性能。
大致的步骤是:浏览器解析HTML代码,创建DOM,HTML也可以请求JS,反过来说JS也可以操纵DOM,HTML也包含或者请求样式,依次构建CSSOM
浏览器引擎将两者结合起来以创建 Render Tree (渲染树),Layout(布局)确定页面上所有内容的大小和位置,确定布局后,将像素 Paint (绘制)到屏幕上。
大概过程如图:
优化关键渲染路径可以缩短首次渲染的时间。了解和优化关键渲染路径对于确保重排和重绘可以每秒 60 帧的速度进行很重要。
详细过程:
生成 DOM
DOM 构建是增量的。浏览器从远程下载 Byte => 根据相应的编码 (如 utf8) 转化为字符串 => 通过 AST(Abstract Syntax Tree,抽象语法树) 解析为 Token => 生成 Node => 生成 DOM。
单个 DOM 节点以 startTag token 开始,以 endTag token 结束。节点包含有关 HTML 元素的所有相关信息。该信息是使用 token 描述的。节点根据 token 层次结构连接到 DOM 树中。
如果另一组 startTag 和 endTag token 位于一组 startTag 和 endTag 之间,则在节点内有一个节点,这就是我们定义 DOM 树层次结构的方式。
生成 CSSOM
浏览器解析 css 文件,生成 CSSOM。CSSOM 包含了页面所有的样式,也就是如何展示 DOM 的信息。
CSSOM 跟 DOM 很像,但是不同。
DOM 构造是增量的,CSSOM 却不是。CSS 是渲染阻塞的:浏览器会阻塞页面渲染直到它接收和执行了所有的 CSS。
CSS 是渲染阻塞是因为规则可以被覆盖,所以内容不能被渲染直到 CSSOM 的完成。
Render Tree
渲染树(Render Tree)包括了内容和样式:DOM 和 CSSOM 树结合为渲染树。
为了构造渲染树,浏览器检查每个节点,从 DOM 树的根节点开始,并且决定哪些 CSS 规则被添加。
渲染树只包含了可见内容(body 里的部分)。
Head(通常)不包含任何可见信息,因此不会被包含在渲染树中。如果有元素上有 display: none;,它本身和其后代都不会出现在渲染树中。
Layout 布局
当渲染树生成,此时便可以布局,决定在页面中元素的大小、尺寸、位置,显示在页面的什么位置,以及每个元素之间的相关性,但是页面的渲染在不同的机型上是不一样的,因此在布局之前所有的步骤都是一样的,只是在布局时需要对屏幕尺寸做差异化的处理。
Paint 绘制
将元素转换成实际像素,绘制在屏幕上,当页面完成了渲染树的创建并完成了布局,此时便可以绘制到屏幕上,以后只有在受影响的区域会发生重绘,此时浏览器就会优化成只需要重绘需要绘制的那个最小区域。
但是需要注意的是,绘制的过程事实上很快,因此在我们做性能的提升时,只聚焦在这一部分并不是最优解。
当我们真正理解了关键路径渲染之后,我们再来看看重排和重绘
重排(Reflow)
:元素的位置发生变动时发生重排,也叫回流。此时在Layout布局阶段,计算每一个元素在设备视口内的确切位置和大小
。当一个元素位置发生变化时,其父元素及其后边的元素位置都可能发生变化,代价极高。
因此,当我们和面试官谈论什么是重排时,重点不在元素的大小、位置...发生变化,这只是为什么
会发生重排,我们应该回答的是什么是
重排:因此我们可以回答:重新计算元素在设备视口内的确切位置和大小。
重绘,我们回答的关键点就应该落到关键渲染路径中的Paint阶段,将我们的渲染树,每一个节点转换成屏幕上的实际像素点
注意我们的答题思路
:
是什么、为什么、怎么做,换句话说应该是是什么
,有什么特点
,有什么应用场景
。
聊完了HTML、CSS,再来聊一聊关键渲染路径中的JavaScript。
JavaScript的执行,落在渲染树生成之前,这便是为什么JavaScript加载、解析、执行这一线程,会阻塞html、css���程,如果没有做阻塞,js可以操作dom,html生成dom,js修改dom,这就乱套了。这也是为什么js中无法获取js后面的元素,因为dom还没有构建完。
因此我们可以尽量把js放在后边,在dom加载完成再去加载js
性能优化上我们能做什么?
- 减少 DOM 树渲染时间(譬如降低 HTML 层级、标签尽量语义化等等)。
- 减少 CSSOM 树渲染时间(降低选择器层级等等)。
- 减少 HTTP 请求次数及请求大小。
- 将 css 放在页面开始位置。
- 将 js 放在页面底部位置,并尽可能用 defer 或者 async 避免阻塞的 js 加载,确保 DOM 树生成完才会去加载 JS。
- 用 link 替代@import。
- 如果页面 css 较少,尽量使用内嵌式。
- 为了减少白屏时间,页面加载时先快速生成一个 DOM 树。
小结
重排和重绘是浏览器关键渲染路径上的两个节点, 浏览器的关键渲染路径就是 DOM 和 CSSOM 生成渲染树,然后根据渲染树通过一个布局(也叫 layout)步骤来确定页面上所有内容的大小和位置,确定布局后,将像素 绘制 (也叫 Paint)到屏幕上。
其中重排就是当元素的位置发生变动的时候,浏览器重新执行布局这个步骤,来重新确定页面上内容的大小和位置,确定完之后就会进行重新绘制到屏幕上,所以重排一定会导致重绘。
如果元素位置没有发生变动,仅仅只是样式发生变动,这个时候浏览器重新渲染的时候会跳过布局步骤,直接进入绘制步骤,这就是重绘,所以重绘不一定会导致重排。
对于性能问题上,减少重绘和回流感觉没有那么重要,因为优化一般情况不是很明显,不答问题也不大,更多的性能优化是在整个链路上的优化,比如性能优化标题里面的那 8 个点。