浏览器加载页面的过程中主要经历下面几个步骤:
- 渲染引擎会解析 HTML 文档来构建 DOM 树
- 同时,渲染引擎用 CSS 解析器解析 CSS 文档构建 CSSOM 树
- 接着,DOM 树和 CSSOM 树关联起来构成渲染树(RenderTree)
- 浏览器按照 renderTree 进行布局 Layout,通过绘制显示出整个页面
上述过程中,就涉及到了重排(reflow)和重绘(repaint)
重排 reflow:是引起 DOM 树重新计算的行为
顾名思义,排位置。增删改节点等引起DOM元素变化,渲染树需要重新计算。
重绘 repaint:一个元素外观的改变(如 color)所触发的浏览器行为
顾名思义,绘制外观。修改背景色等样式变化,使浏览器需要根据新的属性进行绘制。
因此,重绘不一定导致重排,但重排一定会导致重绘。重排会产生比重绘更大的开销。
以下,我们简单的做下重排重绘的性能测试:给页面插入500个li节点,并改变样式:
- 单次插入1个 插入500次
function singleAppendAction() {
console.time('singleAppendAction')
cl.innerHTML = "";
for (let i = 0; i < len; i++) {
let oneLi = document.createElement("li");
oneLi.innerHTML = i + 1;
cl.appendChild(oneLi)
}
console.timeEnd('singleAppendAction')
}
- 一次性插入500个节点
function allAppend() {
console.time('allAppendAction')
cr.innerHTML = "";
let html = "";
for (let i = 0; i < len; i++) {
html += `<li>${i+1}</li>`
}
cr.innerHTML = html;
console.timeEnd('allAppendAction')
}
耗时比较:

再做个测试,也是单次和一次性修改元素的样式
function singleChangeStyle() {
console.time('singleChangeStyle')
let lis = cl.getElementsByTagName("li");
for (let li of lis) {
li.style.fontSize = "20px";
li.style.color = "#f40";
li.style.background = "#abc";
}
console.timeEnd('singleChangeStyle')
}
function allChangeStyle() {
console.time('allChangeStyle')
let lis = cr.getElementsByTagName("li");
for (let li of lis) {
li.style = {
fontSize: "20px",
color: "#f40",
background: "#abc"
}
}
console.timeEnd('allChangeStyle')
}
耗时比较:

由此可以得出,我们在实际生产中要严格注意减少重排重绘的触发。
触发重排的操作主要是几何因素:
- 页面第一次渲染 在页面发生首次渲染的时候,所有组件都要进行首次布局,这是开销最大的一次重排。
- 浏览器窗口尺寸改变
- 元素位置和尺寸发生改变的时候
- 新增和删除可见元素
- 内容发生改变(文字数量或图片大小等等)
- 元素字体大小变化。
- 激活 CSS 伪类(例如::hover)。
- 设置 style 属性
- 查询某些属性或调用某些方法。比如说: offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight 除此之外,当我们调用 getComputedStyle 方法,或者 IE 里的 currentStyle 时,也会触发重排,原理是一样的,都为求一个“即时性”和“准确性”。
触发重绘的操作主要有:
visibility、outline、背景色等属性的改变
优化方案:
- 分离读写操作
div.style.top = "10px";
div.style.bottom = "10px";
console.log(div.offsetWidth);
console.log(div.offseHeight);
- 样式集中改变
div.style = {
top:'10px',
bottom:'10px'
}
- 缓存布局信息
// bad 强制刷新 触发两次重排
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';
// good 缓存布局信息 相当于读写分离
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';
- DOM离线化,使用 display:none
例如,我们要在<ul>中加入100个<li>的话,正常来说每个append会导致一次重拍,
优化方法:即是将<ul>设置为display:none;append完之后再设置为block
- 将position属性设置为absolute或fixed,脱离文档流
- 启用GPU加速