前置知识
浏览器渲染机制
浏览器采用流式布局模型(Flow Based Layout)
浏览器会把 HTML 解析成 DOM,把 CSS 解析成 CSSOM,DOM 和 CSSOM 合并就产生了渲染树(Render Tree)。
有了 RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制到页面上。
由于浏览器使用流式布局,对 Render Tree 的计算通常只需要遍历一次就可以完成,但 table 及其内部元素除外,他们可能需要多次计算,通常要花3 倍于同等元素的时间,这也是为什么要避免使用 table 布局的原因之一。
怎么理解回流(重排)和重绘?什么场景下会触发?如何避免?
- 是什么
- 回流(重排)
- 指的是处于文档流中 DOM 的尺寸大小、位置或者某些属性发生变化时,导致浏览器重新渲染部分或全部文档的情况
- 重绘
- 指的是当页面中的元素不脱离文档流,而简单地进行样式的变化,比如修改颜色、背景等,浏览器重新绘制样式
- 回流(重排)
- 浏览器优化
- 现代浏览器大多都是通过队列机制来批量更新布局,浏览器会把修改操作放在队列中,至少一个浏览器刷新(即16.6ms)才会清空队列,但当你获取布局信息的时候,队列中可能有会影响这些属性或方法返回值的操作,即使没有,浏览器也会强制清空队列,触发回流与重绘来确保返回正确的值。
- 何时触发
- 回流(重排)
- 回流这一阶段主要是计算节点的位置和几何信息,那么当页面布局和几何信息发生变化的时候,就需要回流,如下面情况:
- 添加或删除可见的DOM元素
- 元素的位置发生变化
- 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
- 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代
- 页面一开始渲染的时候(这避免不了)
- 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
- 获取一些特定属性的值
- offsetTop、offsetLeft、 offsetWidth、offsetHeight
- scrollTop、scrollLeft、scrollWidth、scrollHeight
- clientTop、clientLeft、clientWidth、clientHeight
- width、height
- 原因:
- 这些属性有一个共性,就是需要通过即时计算得到。因此浏览器为了获取这些值,也会进行回流。除此还包括 getComputedStyle、getBoundingClientRect() 方法,原理是一样的
- 所以,我们应该避免频繁的使用上述的属性,他们都会强制渲染刷新队列
- 回流这一阶段主要是计算节点的位置和几何信息,那么当页面布局和几何信息发生变化的时候,就需要回流,如下面情况:
- 重绘
- 触发回流一定会触发重绘
- 可以把页面理解为一个黑板,黑板上有一朵画好的小花。现在我们要把这朵从左边移到了右边,那我们要先确定好右边的具体位置,画好形状(回流),再画上它原有的颜色(重绘)
- 颜色的修改
- 文本方向的改变
- 阴影的修改
- 背景的改变
- 触发回流一定会触发重绘
- 回流(重排)
- 如何避免
- HTML
- 避免使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局。
- CSS
- 避免设置多层内联样式,CSS 选择符从右往左匹配查找,避免节点层级过多。
- 如果需要设置动画效果,最好将元素脱离正常的文档流。
- 比如将动画效果应用到 position 属性为 absolute 或 fixed 的元素上,避免影响其他元素的布局,这样只是一个重绘,而不是回流,同时,控制动画速度可以选择 requestAnimationFrame。
- 避免使用 CSS 表达式(例如:calc())。
- 开启 GPU 加速(
transform: translateZ(0)
)
- JS
- 合并多次对 CSS 样式的修改,改成一次处理
<body> <div id="box">文字内容</div> <script> var box = document.getElementById("box"); // box.style.width='100px'; // box.style.height="100px"; // box.style.backgroundColor='red'; box.style.cssText += "width:100px;height:100px;background-color:red"; </script> </body>
- 避免频繁操作样式,最好将样式列表定义为 class 并一次性更改 class 属性。
<style> .box { width: 100px; height: 100px; background-color: red; } </style> <body> <div id="box">文字内容</div> <script> var box = document.getElementById("box"); box.className = "box"; </script> </body>
- 避免频繁操作 DOM ,创建一个 documentFragment ,在它上面应用所有 DOM 操作,最后再把它添加到文档中。
<body> <div id="box"></div> <script> var box = document.getElementById("box"); var fragment = document.createDocumentFragment(); //创建虚拟的节点对象 for (var i = 0; i < 10; i++) { var span = document.createElement("span"); span.index = i; span.style.display = "inline-block"; span.style.width = "20px"; span.style.height = "20px"; span.style.backgroundColor = "red"; span.innerText = span.index; fragment.appendChild(span); } box.appendChild(fragment); </script> </body>
- 可以先为元素设置为不可见:display: none,操作结束后再把它显示出来。
- 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。
- 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素 及后续元素频繁回流。
- HTML
Comments NOTHING