重绘和重排性能优化

  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脱离文档:

  • 隐藏元素, 应用修改, 重新显示
  • 使用文档碎片 在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遍历和重排次数最少.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,612评论 5 471
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,345评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,625评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,022评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,974评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,227评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,688评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,358评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,490评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,402评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,446评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,721评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,802评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,013评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,504评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,080评论 2 341