参考链接:重绘(redraw或repaint),重排(reflow)
lianjie
重绘(redraw/repaint)
重排(reflow)
1、浏览器的运行机制
1、构建DOM树(parse):渲染引擎解析HTML文档,首先将标签转换成DOM树中的DOM node(包括js生成的标签)生成内容树(Content Tree/DOM Tree);
2、构建渲染树(construct):解析对应的CSS样式文件信息(包括js生成的样式和外部css文件),而这些文件信息以及HTML中可见的指令(如),构建渲染树(Rendering Tree/Frame Tree);
3、布局渲染树(reflow/layout):从根节点递归调用,计算每一个元素的大小、位置等,给出每个节点所应该在屏幕上出现的精确坐标;
4、绘制渲染树(paint/repaint):遍历渲染树,使用UI后端层来绘制每个节点。
2、重绘
- 当盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来之后,浏览器便把这些原色都按照各自的特性绘制一遍,将内容呈现在页面上。
- 重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。
- 触发重绘的条件:改变元素外观属性。如:color,background-color等。
注意:table及其内部元素可能需要多次计算才能确定好其在渲染树中节点的属性值,比同等元素要多花两倍时间,这就是我们尽量避免使用table布局页面的原因之一。
3、重排(回流)
当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建, 这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候。
- 重绘和重排的关系:在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程称为重绘。
- 所以,重排必定会引发重绘,但重绘不一定会引发重排。
- 触发重排的条件:任何页面布局和几何属性的改变都会触发重排,比如:
1、页面渲染初始化;(无法避免)
2、添加或删除可见的DOM元素;
3、元素位置的改变,或者使用动画;
4、元素尺寸的改变——大小,外边距,边框;
5、浏览器窗口尺寸的变化(resize事件发生时);
6、填充内容的改变,比如文本的改变或图片大小改变而引起的计算值宽度和高度的改变;
7、读取某些元素属性:(offsetLeft/Top/Height/Width, clientTop/Left/Width/Height, scrollTop/Left/Width/Height, width/height, getComputedStyle(), currentStyle(IE) )
区别:重绘不一定需要重排(颜色的改变等),重排必然导致重绘(改变dom节点位置等)
优化
浏览器:浏览器会维护一个队列,把所有会引起重排,重绘的操作放入这个队列,等队列中的操作到一定数量或者到了一定时间间隔,浏览器就会flush队列,批处理这样多次重排,重绘编程一次重排重绘。
减少重排、重绘
不要一条条修改dom的样式,可以直接定义好css的class,然后修改dom的classname
不要把dom节点的属性值放在一个循环里当成循环里的变量
为动画的 HTML 元件使用 fixed 或 absoult 的 position,那么修改他们的 CSS 是不会 reflow 的。
千万不要使用 table 布局。因为可能很小的一个小改动会造成整个 table 的重新布局。(table及其内部元素除外,它可能需要多次计算才能确定好其在渲染树中节点的属性,通常要花3倍于同等元素的时间。这也是为什么我们要避免使用table做布局的一个原因。)
不要在布局信息改变的时候做查询(会导致渲染队列强制刷新)
优化实现:
<body> <!-- 链接:https://www.cnblogs.com/soyxiaobi/p/9963019.html --> <!-- 重排也可以叫回流 --> <ul id="mylist"> <li><a href="https://www.mi.com">xiaomi</a></li> <li><a href="https://www.miui.com">miui</a></li> </ul> </body> <script> //js要添加的信息 let data = [ { name: 'tom', url: 'https://www.baidu.com', }, { name: 'ann', url: 'https://www.techFE.com' } ]; function addNode(node, data) { var a, li; for (let i = 0, max = data.length; i < max; i++) { a = document.createElement('a'); li = document.createElement('li'); a.href = data[i].url; a.appendChild(document.createTextNode(data[i].name)); li.appendChild(a); node.appendChild(li); } } //不考虑重排的写法,添加的方法 // 每次插入一个新的节点都会造成一次重排 // let ul=document.getElementById('mylist'); // addNode(ul,data); // 方法-:考虑重排,使用批量修改dom的优化手段来重构 // 这种方法造成两次重排,分别是控制元素的显示和隐藏 // 当display为none时,元素就不在文档流了 // let ul = document.getElementById('mylist'); // ul.style.display = 'none'; // addNode(ul, data); // ul.style.display = 'block'; // 方法二:碎片文档,只涉及一次重排 let ul = document.getElementById('mylist'); let fragment = document.createDocumentFragment(); addNode(fragment, data); ul.appendChild(fragment); // 方法三:缓存布局信息 // 因为访问offsetleft,clienttop这些属性时,会引起重排,所以我们应该尽量减少对布局信息的查询次数 // 查询时,将其赋值给局部变量,使用局部变量参与计算 // 一开始: // div.style.left=1+div.offsetLeft+'px'; // div.style.top=1+div.offsetTop+'px'; // 应该 current = div.offsetLeft; div.style.left = 1 + ++current + 'px'; div.style.top = 1 + ++current + 'px'; </script>