媒介
当代web框架大多都是数据驱动类的,比方 react, vue,所以开发者不需要直接打仗 DOM,修正 data 便能够驱动界面更新。然则作为前端工程师,相识浏览器的重绘与重排照样很有必要的,能够协助我们写出更好机能的 web 运用。
浏览器的衬着
- CSS Tree: 浏览器将 CSS 剖析成 CSSOM 的树形组织
- DOM Tree:浏览器将 HTML 剖析成树形的数据组织
- Render Tree:将 DOM 与 CSSOM 兼并成一个衬着树
有了衬着树(Render Tree),浏览器就晓得网页中有哪些节点,以及各个节点与 CSS 的关联,从而晓得每一个节点的位置和多少属性,然后绘制页面。
重绘与重排
当 DOM 的变化影响了元素的多少属性(比方 width 和 height ),就会致使浏览器从新盘算元素的多少属性,一样遭到该元素影响的其他元素也会发作从新盘算。此时,浏览器会使衬着树中遭到影响的部份失效,并从新组织衬着树。这个历程被称为重排(也叫“回流”)(reflow),完成重排以后,浏览器会从新绘制受影响的部份到页面上,这个历程就是重绘(repaint)。
所以重排肯定会引发重绘,而重绘不肯定会引发重排,比方一个元素的转变并没有影响规划的转变(background-color的转变),在这类情况下,只会发作一个重绘(不需要重排)。
引发重排的要素
能够总结出,当元素的多少属性或页面规划发作转变就会引发重排,比方:
- 对可见 DOM 元素的操纵(增加,删除或递次变化)
- 元素位置发作转变
- 元素的多少属性发作转变(比方:外边距、内边距、边框宽度以及内容转变引发的宽高的转变)
- 页面初次衬着
- 伪类款式激活(hover等)
- 浏览器视口尺寸发作转变(转动或缩放)
怎样优化
重绘与重排都是价值高贵的操纵(由于每次重排都邑发生盘算斲丧),它们会致使 web 运用的 UI 反应迟钝,所以开发者在编写运用程序的时刻应当只管削减这类历程的发作。
衬着树行列
由于过量的重绘与重排能够会致使运用的卡顿,所以浏览器会对这个有一个优化的历程。大多数浏览器会经由过程行列化来批量实行(比方把剧本对 DOM 的修正放入一个行列,在行列一切操纵都完毕后再举行一次绘制)。然则开发者偶然能够不知不觉的强迫革新衬着行列来马上举行重排重绘,比方猎取页面规划信息会致使衬着行列的强迫革新,以下属性或要领会马上触发页面绘制:
- offsetTop、offsetLeft、offsetWidth、offsetHeight
- scrollTop、scrollLeft、scrollWidth、scrollHeight
- clientTop、clientLeft、clientWidth、clientHeight
- getComputedStyle()
以上属性和要领都是要浏览器返回最新的规划信息,所以浏览器会马上实行衬着行列中的“待处置惩罚变化”, 并触发重排重绘然后返回最新的值。所以在修正款式的历程当中,应当只管防止运用以上属性和要领。
削减重绘与重排
为了削减重绘重排的发作次数,开发者应当兼并屡次对 DOM 的修正和对款式的修正,然后一次性处置惩罚。
兼并款式操纵
比方:
var el = document.querySelector('div');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
能够兼并成:
var el = document.querySelector('div');
el.style.cssText = 'border-left: 1px; border-right: 1px; padding: 5px;'
批量修正DOM
使元素离开文档流,再对其举行操纵,然后再把元素带回文档中,这类方法能够有用削减重绘重排的次数。有三种基础方法能够使元素离开文档流:
隐蔽元素,运用修正,从新显现
var ul = document.querySelector('ul');
ul.style.display = 'none';
// code... 对ul举行DOM操纵
ul.style.display = 'block';
运用文档片断(document fragment),构建一个空缺文档举行 DOM 操纵,然后再放回原文档中
var fragment = document.createDocumentFragment();
// code... 对fragment举行DOM操纵
var ul = document.querySelector('ul');
ul.appendChild(fragment)
拷贝要修正的元素到一个离开文档流的节点中,修正副本,然后再替代原始元素
var ul = document.querySelector('ul');
var cloneUl = ul.cloneNode(true);
// code... 对clone节点举行DOM操纵
ul.parentNode.replaceChild(cloneUl, ul)
缓存规划信息
前面已晓得,猎取页面规划信息,会致使浏览器强迫革新衬着行列。所以削减这些操纵黑白常有必要的,开发者能够将第一次猎取到的页面信息缓存到局部变量中,然后再操纵局部变量,比方下面的伪代码示例:
// 低效的
element.style.left = 1 + element.offsetLeft + 'px';
element.style.top = 1 + element.offsetTop + 'px';
if (element.offsetTop > 500) {
stopAnimation();
}
// 高效的
var offsetLeft = element.offsetLeft;
var offsetTop = element.offsetTop;
offsetLeft++;
offsetTop++;
element.style.left = offsetLeft + 'px';
element.style.top = offsetTop + 'px';
if (offsetTop > 500) {
stopAnimation();
}
总结
为了削减重绘重排带来的机能斲丧,能够经由过程以下几点改良 web 运用:
- 批量修正 DOM 和款式
- “离线”操纵 DOM 树,离开文档流
- 缓存到局部变量,削减页面规划信息的接见次数
参考