回流与重绘

一、概念

回流:

        当我们对 DOM 的修改引发了 DOM 几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性(其他元素的几何属性和位置也会因此受到影响),然后再将计算的结果绘制出来。这个过程就是回流(也叫重排)。

引发回流的操作:

1.修改dom元素。当一个DOM元素的几何属性发生变化时,所有和它相关的节点(比如父子节点、兄弟节点等)的几何属性都需要进行重新计算。

2.页面首次渲染

3.浏览器窗口大小发生改变 

4.元素的尺寸和位置发生改变(width、height、padding、margin、left、top、border )等等

重绘:

       当我们对 DOM 的修改导致了样式的变化、却并未影响其几何属性(比如修改了颜色或背景色)时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式(跳过了上图所示的回流环节)。这个过程叫做重绘。

一句话:回流必将引起重绘,重绘不一定会引起回流

二、浏览器的优化机制(flush队列

        如果每次 DOM 操作都即时地反馈一次回流或重绘,那么性能上来说是扛不住的。所以很多浏览器都会优化这些操作,于是它自己缓存了一个 flush 队列,把所有会引起回流、重绘的操作放入这个队列,待到队列里的任务多起来、或者达到了一定的时间间隔,或者“不得已”的时候,再将这些任务一口气出队。 这样就会让多次的回流、重绘变成一次回流重绘。

注意: 队列强制刷新

因为有的时候我们需要精确获取某些样式信息,例如:

offsetTop, offsetLeft, offsetWidth, offsetHeight scrollTop/Left/Width/Height clientTop/Left/Width/Height width,height 请求了getComputedStyle(), 或者 IE的 currentStyle 这个时候,浏览器为了反馈最精确的信息,需要立即回流重绘一次,确保给到我们的信息是准确的,所以可能导致 flush 队列提前执行了。

三、如何避免回流和重绘

css:

 1、避免使用table布局。 2、尽可能在DOM树的最末端改变class。 3、避免设置多层内联样式。 4、将动画效果应用到position属性为absolute或fixed的元素上。 5、避免使用CSS表达式(例如:calc())。

 js:

1、避免频繁操作样式,最好一次性重写style属性,或者将样式列表定义为class并一次性更改class属性。 2、避免频繁操作DOM,创建一个documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中。 3、也可以先为元素设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。 4、避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来。 5、对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。

例1:变量的形式缓存起来 ,避免频繁改动

有时我们想要通过多次计算得到一个元素的布局位置,我们可能会这样做:




  
  
  
  Document
  


  

这样做,每次循环都需要获取多次“敏感属性”,是比较糟糕的。我们可以将其以 JS 变量的形式缓存起来,待计算完毕再提交给浏览器发出重计算请求:

// 缓存offsetLeft与offsetTop的值
const el = document.getElementById('el') 
let offLeft = el.offsetLeft, offTop = el.offsetTop

// 在JS层面进行计算
for(let i=0;i<10;i++) {
  offLeft += 10
  offTop  += 10
}

// 一次性将计算结果应用到DOM上
el.style.left = offLeft + "px"
el.style.top = offTop  + "px"

例2:避免逐条改变样式,使用类名去合并样式

const container = document.getElementById('container')
container.style.width = '100px'
container.style.height = '200px'
container.style.border = '10px solid red'
container.style.color = 'red'

  每次单独操作,都去触发一次渲染树更改,从而导致相应的回流与重绘过程。

 优化成一个有 class 加持的样子:

  • 使用classList



  
  
  
  Document
  


  
  • 使用cssText
 const container = document.getElementById('container')
 container.style.cssText += 'width: 100px; height: 200px; border: 10px solid red; color: red;';

合并之后,等于我们将所有的更改一次性发出,用一个 style 请求解决掉了。

例3:将dom离线

我们给元素设置 display: none,将其从页面上“拿掉”,将无法触发回流与重绘——这个将元素“拿掉”的操作,就叫做 DOM 离线。

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');
appendDataToElement(ul, data);

如果我们直接这样执行的话,由于每次循环都会插入一个新的节点,会导致浏览器回流一次。

优化操作:隐藏元素,应用修改,重新显示

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';

当我们进行少量 DOM 操作时,DOM 离线化的优越性确实不太明显。一旦操作频繁起来,这“拿掉”和“放回”的开销都将会是非常值得的。

参考:

https://juejin.im/post/6844903987091603463#heading-4

https://juejin.im/post/6844903942137053192#heading-6

https://juejin.im/post/6844903656836317198#heading-6

你可能感兴趣的:(javascript)