一.需要先了解一下浏览器的渲染机制
从上面这个图,我们可以看到,浏览器渲染过程如下:
-
解析HTML,生成DOM树,解析CSS,生成CSS OM
-
将DO M树和CSSOM树结合,生成渲染树
-
回流:根据生成的渲染树进行回流,得到节点的位置,大小等
-
重绘:根据渲染树以及回流得到的信息进行绘制
-
最后展示在页面上
回流一定会引起重绘,重绘不一定会引起回流
看一下DOM树和CSSOM树是怎么样合成为渲染树的
浏览器在把这两个树进行合成渲染树的过程中会确定我们的节点是否可见,和各个节点的样式并精确他们的位置
这里可以看到,如果对一个元素使用了display:none,那么在渲染树中是不存在这个元素的。可知渲染树只包含可见节点
二、回流
当我们渲染树中的一些元素的结构或者尺寸等发生改变,浏览器重新渲染 部分或者全部文档的过程就叫做回流。
会导致回流的操作:
- 页面首次渲染
- 浏览器窗口大小发生变化(如果是按照百分比布局的话,元素的宽度发生变化,就相当于尺寸发生变化,会引起回流)
- 内容变换 ,比如文本变化或图片被另一个不同尺寸的图片所替代
- 添加或者删除可见的节点
- 元素的位置发生变化
- 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
…反正布局发生改变,就会导致回流
| 常见的回流元素 | |||
|---|---|---|---|
| width | height | padding | margin |
| display | border-width | border | top |
| position | font-size | float | text-align |
| overflow-y | font-weight | overflow | left |
| font-family | line-height | vertical-align | right |
| clear | white-space | bottom | min-height |
三、重绘
当页面中元素样式的改变不影响它在文档流中的位置,浏览器会将样式复制给元素,这个过程叫做重绘。
| 常见的重绘元素 | |||
|---|---|---|---|
| color | border-style | visibility | background |
| text-decoration | background-image | background-position | background-repeat |
| outline-color | outline | outline-style | border-radius |
| outline-width | box-shadow | background-size |
四、如何减少回流、重绘
-
合并对DOM样式的修改,采用css class来修改
const box = document.querySelector('.box')再 box.style.margin = '5px' box.style.borderRadius = '12px' box.style.boxShadow = '1px 3px 4px #ccc'建议使用css class
.active{ margin: 5px; border-dadius: 12px; box-shadow: 1px 3px 4px #ccc} const box = document.querySelector('.box').classList.add('active') -
将DOM离线
//先为元素设置display:none操作结束后再把它显示出来。 //因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘, //操作完之后再将display改为显示,只会触发一次回流与重绘 let container = document.getElementById('container') container.style.display = 'none' container.style.width = '100px' container.style.height = '200px' container.style.border = '10px solid red' container.style.color = 'red' ...(省略了许多类似的后续操作) container.style.display = 'block' -
当需要对多个DOM进行操作的时候,避免直接对单个DOM进行操作
//使用 createDocumentFragment (创建文档碎片节点)方法创建虚拟的 dom 对象 // 这样的能将对 dom 的多次修改合并为一次,就能减少回流和重绘的次数 let ul = document.querySelector('ul') let box = document.createDocumentFragment() for (let i = 0; i < 5; i++) { let li = document.createElement("li") li.appendChild(document.createTextNode(i)) box.appendChild(li) } ul.appendChild(box) //createDocumentFragment使用appendChild追加子元素时, //如果把它追加进页面中,则插入的是它本身和它的所有子孙节点 -
不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局,如果一个表很大的话,浏览器会等全部渲染好在进行下一步,这个等待的过程会降低用户体验
-
尽可能不要修改影响范围比较大的 DOM,就是如果你要改变子元素的样式,就不要去它的父元素设置,不通过父元素去影响子元素,直接修改子元素。
-
动画实现速度的选择,动画速度越快,回流次数越多,也可以选择使用 requestAnimationFrame(请求动画帧),将不会进行重绘和回流
…还有很多其他减少回流和重绘的办法,等以后工作中可以慢慢累积
总之:回流的性能消耗要比重绘大,能重绘就不回流,能不重绘就不重绘