重绘和重排性能优化

2020-06-07  本文已影响0人  小宇cool
  1. 重绘和重排

1.1 DOM树和渲染树

浏览器下载完页面中的所有组件、HTML标记,javascript,css图片、之后会解析并生成两个内部的数据结构

DOM树:

​ 表示页面结构

渲染树:

​ 表示DOM节点如何显示

DOM树中每一个需要显示的节点在渲染树中至少存在一个对应的节点,(隐藏的DOM元素没有对应的节点).渲染树中的节点被称为.一旦DOM树和渲染树构建起来,浏览器就会显示(绘制"paint") 页面元素.

1.2 什么是重绘和重排

当DOM的变化改变了元素的几何属性,例如宽和高,或者改变了边框高度,给段落增加文本,等等等.都会导致浏览器需要重新计算元素的几何属性,同样其他的元素的几何属性个位置也会因此受到影响,浏览器会使渲染树受到影响的部分失效, 并重新构建渲染树, 这个过程称为 "重排". 完成重排后, 浏览器会重新绘制受影响的部分到屏幕中,该过程称为"重绘".

并不是所有的DOM变化都会影响几何属性,例如改变一个元素的背景色并不会改变他的宽和高, 在这种情况下,只会发生一次重绘,因为页面的布局并没有改变.

重绘和重排都是代价昂贵的操作,它们会导致web应用程序的UI反应迟钝, 所以尽可能得减少这类操作的发生.

1.3 重排何时发生

根据改变的范围和程度, 渲染树或大或小对应的部分都需要重新计算然后渲染到页面上, 有些改变会触发整个页面的重排,

例如当滚动条出现时.

1.4 最小化重绘和重排

重绘和重排对性能的消耗十分昂贵,因此一个好的提高程序响应速度的方法减少减少此类操作的发生.

为了减少发生次数,应该合并多次对DOM和样式的修改,然后一次性处理掉.

通过DOM改变元素样式:

let el = document.getElementById("div")
el.style.width = "434px";
el.style.padding = "4px";
el.style.borderLeft = "6px";

示例中三个样式属性被改变, 每一次都影响了元素的几何结构.最糟糕的情况下,会导致浏览器触发三次重排.大部分现代浏览器为此做了优化,只会触发一次重排.但是在旧版本浏览器中仍然效率低下.

一个能够得到同样效果且效率更高的方式是: 合并所有的改变然后一次性处理,这样只会改变DOM一次.使用cssText属性可以实现.

let el = document.getElementById("div")
el.style.cssText = 'width:12px; height:46px; padding:4px'

例子上的代码修改了cssText属性并覆盖了已存在的样式信息, 因此如果向保留现有样式,可以把他附加到cssText字符串后面.

el.style.cssText += 'padding-left:5px'

​ 另外一种一次性改变元素元素的方法是修改css的class类名 ,而不是修改内联样式改变css的Clss名称的方法更清晰,更易于维护.

let el = document.getElementById("div")
el.className = 'style';
el.classList.add("active")

1.5 批量修改DOM

当年需要对DOM元素进行一系列操作时,可以通过以下步骤来减少重绘和重排的次数:

  1. 使元素脱离文档流
  2. 对其应用进行多重改变
  3. 将文档带回文档流

该过程里会触发两次重排---> 第一步和第三步. 如果你忽略第这两个步骤, 那么在第二步所产生的任何修改都会触发一次重排

有三种基本方法可以使DOM脱离文档:

假设我们有一个普通的列表文本,我们要更新其中的文本内容

   <ul id="myList"> 
        <li> text1 </li>
        <li> text2 </li>
    </ul>                    

假设数据已经定义到一个数组对象中,要插入这些列表. 这些数据定义如下

let data = [ {text:"text3"}, {text:"text4"}]

下面是是一个用来更新指定节点数据通用函数

function updatePageData(appendEle,data){
    let li;
    for(let i = 0,max = data.length; i < max;i++){
        li = document.createElement("li");
        li.textContent = data[i].text;
        appendEle.append(li)
    }
}

我们通过会使用下面这种正常的方法调用这个函数

let myList = document.getElementById("myList");
updatePageData(myList,dataArr)

然而使用上面这种方法,data数据中的每一次数据被附加到当前DOM树,都会导致一次重排.正如上文所讲到的,一种减少

重排的方法是通过改变display属性, 临时从文档中移除ul元素, 然后在恢复他

let myList = document.getElementById("myList");
myList.style.display = 'none'
updatePageData(myList,dataArr);
myList.style.display = 'block';

另一种改变重排的次数:在文档之外创建一个文档碎片,然后把它附加到原始列表中

let fragment = document.createDocumentFragment();
updatePageData(fragment,dataArr);
myList.append(fragment); 

第三种解决方案是为需要修改的节点创建一个备份. 然后对副本进行操作, 一旦操作完成,就有新的节点替代旧的节点.

let myList = document.getElementById("myList");
let clone = myList.cloneNode(true);
updatePageData(clone,dataArr);
myList.parentNode.replaceChild(clone, myList)

我们推荐使用第二个方案( 文档碎片) 因为它所产生的DOM遍历和重排次数最少.

上一篇 下一篇

猜你喜欢

热点阅读