在开发前端页面时,性能优化是我们非常关注的领域。尤其是在复杂的用户界面中,如何避免不必要的性能损失至关重要。在浏览器渲染过程中,有几个概念对性能影响尤为重要,其中包括“回流”和“重绘”。
1. 浏览器渲染的过程
浏览器的渲染流程从接收到HTML文件开始,经过多个步骤,最终展示出我们看到的页面。简要来说,浏览器的渲染流程如下:
- 解析数据包:浏览器首先接收并解析网络请求的响应数据包,得到HTML文件和相关的CSS文件。
- 构建DOM树:浏览器将HTML文件转化为DOM(文档对象模型)树。每一个HTML标签都会转化为一个节点。
- 构建CSSOM树:CSS文件中的样式会被解析并生成CSSOM(CSS对象模型)树。
- 合成Render树:浏览器将DOM树与CSSOM树合并,得到Render树。这个树代表了可见的页面结构,只有那些会在页面中展示的元素才会出现在渲染树中。
- 计算布局:浏览器开始计算每个元素的布局信息,包括位置、大小等。这个过程也叫“回流”。
- 绘制阶段:通过GPU来绘制页面上的像素,最终将页面呈现出来。
2. 回流与重绘的区别
在浏览器渲染过程中,回流和重绘是两大关键操作。
回流(Reflow)
回流是指浏览器重新计算元素的布局,通常发生在以下情况:
- 页面初次渲染。
- 浏览器窗口大小发生变化。
- 新的DOM元素被添加或移除。
- 元素的几何属性发生改变(如宽高、位置、字体大小等)。
回流是一个非常昂贵的操作,因为它涉及到整个页面的重新布局,可能会影响到页面上的多个元素,因此在频繁发生回流时,页面的性能会大大降低。
重绘(Repaint)
重绘是指浏览器重新绘制元素的样式,但不涉及布局的计算。例如,当我们改变元素的颜色、背景时,就会发生重绘,但不会影响到元素的位置或大小。
那么问题来了,文字的大小是导致回流,还是重绘呢?
三秒钟思考
3.. 2.. 1..
答案是重绘哦。
- 重绘不一定会导致回流。例如,改变文本颜色只会导致重绘,而不会重新计算元素的布局。
问题又又又来了!
接下来这段代码会导致几次回流呢?
element.style.width = '100px'
element.style.height = '200px'
这就涉及到我们接下来要讲的浏览器的渲染优化机制。
3. 浏览器的渲染优化机制
在浏览器中,为了避免频繁的回流和重绘,采用了渲染队列机制。当多个导致回流的操作发生时,浏览器不会立即执行,而是将这些操作放入渲染队列,继续往下执行。当渲染队列积累到一定数量或者页面没有新的回流操作时,浏览器才会一次性执行这些操作,减少性能的消耗。
所以上面的代码只进行了一次回流哦!
4. 强制执行渲染队列的属性
有些属性会导致浏览器强制清空渲染队列进行回流,常见的有以下几种:
- Offset家族:offsetWidth/offsetHeight...
- Client家族:clientWidth/clientHeight...
- Scroll家族:scrollWidth/scrollHeight...
这些属性一旦被访问,浏览器就会立刻清空渲染队列,计算出当前的样式信息,从而触发回流操作。
5. 减少回流的优化策略
为了提高页面性能,我们可以采取一些优化策略,减少不必要的回流操作。
-
先让元素脱离文档流,再修改几何属性,最后再放回文档流
当我们需要修改某个元素的几何属性(如宽高、位置等)时,可以将该元素先隐藏(display: none),修改完属性后再将其恢复(display: block)。这样可以避免页面上其它元素的回流。 -
使用虚拟文档document.createDocumentFragment()
可以将修改操作放在内存中的虚拟DOM中,修改完之后一次性更新到真实的DOM中,减少不必要的回流。 -
使用克隆DOM(clone)
当需要对DOM进行频繁操作时,可以通过克隆DOM节点来避免每次操作都会导致回流。
6. 小练笔
假设我们有如下代码片段:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">Hello, World!</div>
<script>
let el = document.getElementById('app'); //不导致回流
el.style.width = (el.offsetWidth + 1) + 'px'; //强制清空渲染队列,但此时队列里没有
//所以不造成回流
el.style.width = 1 + 'px'; //最后一次清空渲染队列,造成一次回流
</script>
</body>
</html>