DOM – Browser Reflow & Repaint

前言

没有深入研究过, 懂个概念就好, 等性能遇到问题在来看看.

以前写的笔记: 游览器 reflow

 

参考:

reflow和repaint引发的性能问题

精读《web reflow》

 

Reflow & Repaint

repaint 是小的, 性能没有那么伤, 它只是换换颜色之类的.

reflow 是大的, 伤性能, 它会更动到排版布局.

所有插入 dom, 修改 dimension, padding 这类影响 layout 的东西都会导致 reflow.

translate 不会哦, positioin fixed 这种就 ok. 或者说它可以优化. 只是上层渲染, 不会影响整棵树.

 

when to render ?

当 JS 修改 DOM 时, 游览器并不是马上就渲染的, 它会有一个 timing. 

通过 requestanimationframe 可以监听到它渲染了没有. 它不定时的, 依据各种 CPU / GPU 繁忙程度之类的去决定.

所以呢, 可能 JS 修改了多个属性, 但是其实游览器是一次性 reflow, repaint 的.

 

getBoundingClientRect & window.getComputedStyle

有传言说 getBoundingClientRect 和 getComputedStyle 会导致 reflow.

以前我都不太敢用, 后来才明白它的意思是.

如果 JS 修改了 DOM, 在游览器还没有决定什么时候渲染前, JS 又调用了 getBoundingClientRect.

那么游览器就必须马上去渲染, 这样才能给出准确的 rect. 所以说调用后就会 reflow.

但前提是游览器有需要渲染的东西而它还没有执行渲染. 一旦执行了渲染, 你连续调用 100 次 getBoundingClientRect 它也不会再去 reflow 了.

它怎么可能这么蠢呢. 是我以前蠢.. 哈哈

除了 getBoundingClientRect, getComputedStyle 还有许多操作都会导致提前 reflow 哦,比如 scroll,focus 等等。

Best practice

比如我想 resize table 的每一个 column width。

假如我 for loop 每一个 column,get widest cell then update all column cell to widest。

这样就不好,因为 for loop 的每一次都是一读一写,这样会一直 reflow。

好的做法是 for loop 读取每一个 column 的 widest cell,然后再 for loop 一次 update all column cell to widest。

这样就从 "读写(reflow)读写(reflow)读写(reflow)" 变成了 "读读读写写写(reflow)",最终只 reflow 一次。

 

具体例子

有一个 h1

<h1>Hello World</h1>

一开始它是 display none

h1 {
  display: none;
  opacity: 0;
  transition: opacity 5s;
}

h1.showing {
  display: unset;
}

h1.shown {
  opacity: unset;
}

我们同时给它添加 showing 和 shown class

const h1 = document.querySelector('h1')!;
window.setTimeout(() => {
  h1.classList.add('showing');
  h1.classList.add('shown');
}, 1000);

它不会有 fade in 的效果,因为游览器同时渲染了 display block 和 opacity 1。(总共渲染 1 次而已)

在两行代码之间加入一些会导致 reflow 的代码

h1.classList.add('showing');
document.body.offsetHeight;
h1.classList.add('showed');

这时就会有 fade in 效果了,因为第一次渲染发生在 document.body.offsetHeight 之前,它只渲染了 display block。

而第二次才渲染 opacity 1。(总共渲染了 2 次)

但凡我们要拿的 information 涉及到布局,十之八九会导致 reflow。

所以一定要切记,如果我们修改了 DOM 又要拿 information 就要很小心,information 最好是可以提前拿啦 (除非你真的是想立刻拿到修改 和 render 后的 information)。

另外一点,调用 getComputedStyle 函数不会导致 reflow 什么的

h1.classList.add('showing');
window.getComputedStyle(h1);
h1.classList.add('showed');  
// 只会渲染 1 次而已

但是拿 computedStyle 的属性会导致 reflow

h1.classList.add('showing');
window.getComputedStyle(h1).color;
h1.classList.add('showed');
// 会渲染 2 次

所以同样的,要拿 information 请尽早。

我个人的习惯是把 DOM 操作 wrap 在 requestAnimationFrame 里面,然后里面不做任何的 read information,一定是先把要的 information read 出来,最后才 write。

另外一点,query element (e.g. querySelector, querySelectorAll) 是不会导致立刻 reflow 的

const h1 = document.querySelector('h1')!;
window.setTimeout(() => {
  h1.classList.add('showing');
  const ww = document.createElement('div');
  ww.classList.add('showing');
  document.body.appendChild(ww);
  const length = document.body.querySelectorAll('.showing').length;
  window.setTimeout(() => {
    console.log(length);
  }, 1000);
  h1.classList.add('showed');
}, 1000);

上面只会渲染一次,不会有 fade in 效果。(注:console 会导致 reflow 所以我才用 setTimeout wrap 它起来)

 

posted @ 2022-03-11 21:33  兴杰  阅读(77)  评论(0编辑  收藏  举报