JS进阶 - reflow & repaint 的性能优化

页面的渲染过程及优化渲染方式

浏览器内核

目前的 Chrome 浏览器为多进程多线程架构,包括 Browser进程、Renderer进程、GPU进程。

  • 浏览器主进程:负责浏览器界面的显示、各个页面的管理。每次我们打开浏览器,都会启动一个主进程,结束该进程就会关闭我们的浏览器。主进程和渲染进程是分离的,这样可以保证页面的崩溃不会导致浏览器的崩溃。

  • 渲染进程:这是网页的渲染进程,负责页面的渲染工作,一般来说,一个页面都会对应一个渲染进程,各自互相独立互不影响。

  • GPU 进程:如果页面启动了硬件加速,浏览器就会开启一个 GPU 进程,但是最多只能有一个,当且仅当 GPU 硬件加速打开的时候才会被创建。

每个进程都是多线程的,其目的是保持用户界面的高响应度,保证 UI 线程不会被其他操作阻碍从而影响了对用户操作的响应,例如 JS 脚本的解析执行都是在独立的线程中运行的。

页面渲染过程

页面从输入 URL 到页面渲染完成,主要经历了这几个阶段:

  1. DNS 解析
  2. 建立 TCP 连接
  3. 发送 HTTP 请求
  4. 渲染页面
  5. 断开连接

DNS 解析

DNS 解析的顺序依次为:

  • 浏览器的 DNS 缓存,chrome 的缓存有效期为 60s,safari 有效期大概 10 多秒
  • 系统缓存:hosts 文件设置的映射关系
  • 路由器缓存
  • ISP(互联网服务提供商)服务器缓存
  • 根域名服务器缓存(保存着 顶级域名服务器 的ip地址)
  • 顶级域名服务器缓存(例如 .com 域名服务器,保存着 主域名服务器 的ip地址)
  • 主域名服务器缓存(例如 google.com 域名服务器)

渲染页面

浏览器的渲染过程如下图所示:

  1. 解析 HTML,生成 DOM 树,解析 CSS,生成 CSS 规则;
  2. 将 DOM 树和 CSS 规则结合,生成渲染树;
  3. 根据生成的渲染树进行重排(reflow),得到节点的几何信息;
  4. 根据重排获得的数据进行重绘(repaint);
  5. 将像素发送给 GPU 进行展示;

reflow 重排

重排指的是浏览器重新计算文档中元素的几何信息(位置和大小)的过程。当一个元素发生重排时,他的父元素和子元素都可能发生重排。

常见的会导致重排情况:

  • 调整浏览器窗口大小
  • 增加或移出样式表
  • 内容发生变化
  • 改变字体
  • Js 操作 DOM
  • 修改 style 属性的值
  • 读取 offsetclientscroll属性或调用getComputedStyle()getBoundingClientRect()方法
  • CSS 伪类激活,例如 :hover

repaint 重绘

当元素的样式更新时就会触发重绘,当发生 reflow 时,必定触发 repaint。

具体什么属性会触发 reflow 和 repaint 见 csstriggers

优化

避免触发同步布局事件:

浏览器为了防止多次重排操作引发假死,会通过队列化修改并批量执行来优化重排的过程,但如果调用 offsetTop 等属性时,浏览器为了获取到真实的值,会强制队列刷新并立即重排, 所以避免使用以下属性或方法:

  • offsetTop/Left/Widt/Height

  • scrollTop/Left/Width/Height

  • clientTop/Left/Width/Height

  • getComputedStyle()

  • getBoundingClientRect()

如果必须使用这些属性,可以将属性值缓存起来:

const width = box.offsetWidth;
function initP() {
    for (let i = 0; i < paragraphs.length; i++) {
        paragraphs[i].style.width = width + 'px';
    }
}

合并DOM操作

优化重排和重绘最好的方式就是减少他们的发生次数,为此可以将多次DOM操作合并为1次:

const el = document.getElementById('test');
el.style.padding = '5px';
el.style.borderLeft = '1px';
el.style.borderRight = '2px'; // 3次分离的修改触发了3次重排
// 使用cssText或class来合并修改
const el = document.getElementById('test');
el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';
// or
el.className += ' active';

离线DOM操作

如果DOM操作无法合并,可以先将DOM脱离文档流,修改完成后再带回文档中,离线DOM主要有三种方式:

  1. 使用 display:none(两次回流和重绘)

    function appendDataToElement(appendToElement, data) {
        let li;
        for (let i = 0; i < data.length; i++) {
            li = document.createElement('li');
            li.textContent = 'text';
            appendToElement.appendChild(li);
        }
    };
    const ul = document.getElementById('list');
    ul.style.display = 'none';
    appendDataToElement(ul, data);
    ul.style.display = 'block';
    
  2. 使用 DocumentFragment 进行缓存操作(一次回流和重绘)

    const ul = document.getElementById('list');
    const fragment = document.createDocumentFragment();
    appendDataToElement(fragment, data);
    ul.appendChild(fragment);
    
  3. 使用 cloneNode 和 replaceChild(一次回流和重绘)

    const ul = document.getElementById('list');
    const clone = ul.cloneNode(true);
    appendDataToElement(clone, data);
    ul.parentNode.replaceChild(clone, ul);
    

使用CSS硬件加速

使用css3硬件加速,transform、opacity、filters等动画不会引起回流重绘

JS进阶 - reflow & repaint 的性能优化

https://hashencode.github.io/post/9e8699cf/

作者

BiteByte

发布于

2020-08-08

更新于

2024-11-15

许可协议