重排与重绘

172 阅读3分钟

浏览器加载页面的过程中主要经历下面几个步骤:

  • 渲染引擎会解析 HTML 文档来构建 DOM 树
  • 同时,渲染引擎用 CSS 解析器解析 CSS 文档构建 CSSOM 树
  • 接着,DOM 树和 CSSOM 树关联起来构成渲染树(RenderTree)
  • 浏览器按照 renderTree 进行布局 Layout,通过绘制显示出整个页面

上述过程中,就涉及到了重排(reflow)和重绘(repaint)

重排 reflow:是引起 DOM 树重新计算的行为

顾名思义,排位置。增删改节点等引起DOM元素变化,渲染树需要重新计算。

重绘 repaint:一个元素外观的改变(如 color)所触发的浏览器行为

顾名思义,绘制外观。修改背景色等样式变化,使浏览器需要根据新的属性进行绘制。

因此,重绘不一定导致重排,但重排一定会导致重绘。重排会产生比重绘更大的开销。

以下,我们简单的做下重排重绘的性能测试:给页面插入500个li节点,并改变样式:

  1. 单次插入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')
    }
  1. 一次性插入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')
    }

耗时比较:

由此可以得出,我们在实际生产中要严格注意减少重排重绘的触发。

触发重排的操作主要是几何因素:

  1. 页面第一次渲染 在页面发生首次渲染的时候,所有组件都要进行首次布局,这是开销最大的一次重排。
  2. 浏览器窗口尺寸改变
  3. 元素位置和尺寸发生改变的时候
  4. 新增和删除可见元素
  5. 内容发生改变(文字数量或图片大小等等)
  6. 元素字体大小变化。
  7. 激活 CSS 伪类(例如::hover)。
  8. 设置 style 属性
  9. 查询某些属性或调用某些方法。比如说: 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加速