首页 > 技术文章 > 重绘+重排+项目优化

yaya-003 2020-05-09 10:40 原文

先来学习一下浏览器的运行机制

  1.构建DOM树(parse)渲染引擎解析HTML文档,首先将标签转换成DOM树中的DOM node(包括js生成的标签)生成内容树(Content Tree/DOM Tree)

  2.构建渲染树(construct):解析对应的CSS样式文件信息(包括js生成的样式和外部css文件)而这些文件信息以及HTML中可见的指令(如<b></b>),构建渲染树(Rendering Tree/Frame Tree);render tree中每个NODE都会有自己的style,而且render tree不包含隐藏的节点(比如:display:none的节点,还有head节点),因为这些节点不会用于呈现

  3.布局渲染树(reflow/layout):从根节点递归调用,计算每一个元素的大小、位置等,给出每个节点所应该在屏幕上出现的精确坐标

  4.绘制渲染树(paint/repaint):遍历渲染树,使用UI层来绘制每个节点

重绘(repaint或redraw)

  当盒子的位置、大小以及其他属性,例如颜色、字体大小等都确定下来之后,浏览器便把这些原色都按照各自的特性绘制一起,将内容呈现在页面上。

  重绘是指一个元素外观的改变所触发的浏览器行为,浏览器会根据元素的新属性重新绘制,使元素呈现新的外观。

  触发重绘的条件:改变元素的外观属性。比如:color,background-color等

注意:table及其内部元素可能需要多次计算才能确定好其在渲染树中节点的属性值,比同等元素要多花两倍时间,这就是我们尽量避免使用table布局页面的原因之一。

重排(重构/回流/reflow)

  当渲染树中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,这就称为回流(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))

重绘重排的代价:耗时,导致浏览器卡慢 

优化:

  1.浏览器自己的优化:

    浏览器会维护一个队列,把所有的会引起重绘、重排的操作放入这个队列,等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理,这样就会让多次的重绘、重排变成一次重绘重排

  2.认为能做的优化:我们要减少重绘和重排就是要减少对渲染树的操作,则我们可以合并多次的DOM和样式的修改,并减少对style样式的请求

  (1)直接改变元素的className

    (2)display:none;先设置元素为display:none;然后进行页面布局等操作;设置完成后将元素设置为diaplay:block,这样的话就只引发两次重绘和重排

  (3)使用cloneNode(true or false)和replaceChild技术,引发一次重绘和重排

  (4)将需要多次重拍的元素,position属性设置为absolute和fixed,元素脱离了文档流,它的变化不会引起其他元素

  (5)如果需要创建多个DOM节点,可以使用DocumentFragment创建完后一次性的加入document

结合第5点举个应用场景:

var fragment = document.createDocumentFragment()
for(let i = 0;i< 100;i++){
    var li = document.createElement('li')
    li.innerHTML = 'YAYa' + i
    fragment.appendChild(li)
}
document.getElementById('fruit').appendChild(fragment)

 

推荐阅读