我知道的回流和重绘以及优化方案

593 阅读5分钟

回流和重绘是浏览器渲染的机制,是经过服务器Response响应阶段之后进行的渲染机制,相应要了解回流和重绘先看一下浏览器的渲染机制。

浏览器的解析过程

  • 当浏览器拿到一个网页后,首先浏览器会先解析HTML,如果遇到了外链的css,会一下载css,一边解析HTML。
  • 当css下载完成后,会继续解析css,生成css Rules tree,不会影响到HTML的解析。
  • 当遇到script标签时,一旦发现有对javascript的引用,就会立即下载脚本,同时阻断文档的解析,等脚本执行完成后,再开始文档的解析。

浏览器的渲染过程

浏览器渲染过程如下:

1.解析HTML,生成DOM树,解析CSS,生成CSSOM树
2.将DOM树和CSSOM树结合,生成渲染树(Render Tree)
3.Layout(回流):根据生成的渲染树,进行回流(Layout),得到节点的几何信息(位置,大小)
4.Painting(重绘):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
5.Display:将像素发送给GPU,展示在页面上。(这一步其实还有很多内容,比如会在GPU将多个合成层合并为同一个层,并展示在页面中。而css3硬件加速的原理则是新建合成层,这里我们不展开,之后有机会会写一篇博客)

渲染过程看起来很简单,让我们来具体了解下每一步具体做了什么。

  • 生成渲染树

为了构建渲染树,浏览器主要完成了以下工作:

1.从DOM树的根节点开始遍历每个可见节点。
2.对于每个可见的节点,找到CSSOM树中对应的规则,并应用它们。
3.根据每个可见节点以及其对应的样式,组合生成渲染树。

第一步中,既然说到了要遍历可见的节点,那么我们得先知道,什么节点是不可见的。不可见的节点包括: 一些不会渲染输出的节点,比如script、meta、link等。 一些通过css进行隐藏的节点。比如display:none。注意,利用visibility和opacity隐藏的节点,还是会显示在渲染树上的。只有display:none的节点才不会显示在渲染树上。

注意: 渲染树只包含可见的节点

不同内核的浏览器渲染

上图是webkit内核的渲染流程,和总体渲染流程差不多,要构建HTML的DOM Tree,和CSS规则树,然后合并生成Render Tree,最后渲染。

这个是Mozilla的Gecko渲染引擎。
总体看来渲染流程差不多,只不过在生成渲染树或者Frame树时,两者叫法不一致,webkit称之为Layout,Gecko叫做Reflow。

回流reflow&重绘repaints

回流:当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。
重绘:当页面中元素样式的改变并不影响b布局时(eg:color、background-color等),浏览器会将新样式赋予给元素并重新绘制它(但宽高、大小、位置等不变),这个过程称为重绘。

何时发生

  • 页面首次加载
  • 删除,增加,或者修改DOM元素节点。
  • 移动DOM的位置,开启动画的时候。
  • 修改CSS样式,改变元素的大小,位置时,或者将使用display:none时,会造成重排;修改CSS颜色或者visibility:hidden等等,会造成重绘。
  • 修改网页的默认字体时。
  • Resize窗口的时候(移动端没有这个问题),或是滚动的时候。
  • 内容的改变,(用户在输入框中写入内容也会)。
  • 激活伪类,如:hover。
  • 计算offsetWidth和offsetHeight。
  • 调整窗口大小
  • 字体大小
  • 一些常用且会导致回流的属性和方法。
    • offsetTop, offsetLeft, offsetWidth, offsetHeight
    • scrollTop/Left/Width/Height
    • clientTop/Left/Width/Height
    • IE中的 getComputedStyle(), 或 currentStyle

如何避免浏览器的回流&重绘

  • 放弃操作dom,基于vue/react的数据影响视图模式
  • 分离读写操作(利用现代浏览器的渲染队列机制)
box.style.width = '200px'
box.style.hight = '200px'
这是一次线进城

box.style.width = '200px'
box.style.hight = '200px'
console.log(div)
这是两次线进城

将样式进行集中处理,其中一下会刷新渲染队列 offsetTop、offsetLeft、offsetWidth、offsetHeight、clientTop、clientLeft、clientWidth、clientHeight scrollTop、scrollLeft、scrollWidth、scrollHeight、getComputedStyle、currentStyle....

  • 样式集中改变
div.style.cssText = 'width:20px;height:20px;';
div.className = 'box';
  • 尽量避免style的使用,对于需要操作DOM元素节点,重新命名className,更改className名称。
  • 缓存布局信息
bad
div.style.left = div.offsetLeft + 1 + 'px';     
div.style.top = div.offsetTop + 1 + 'px';     

better
var curLeft = div.offsetLeft;   var curTop = div.offsetTop;
div.style.left = curLeft + 1 + 'px';    div.style.top = curTop + 1 + 'px';

不要经常获取同一个元素,可以第一次获取元素后,用变量保存下来,减少遍历时间

  • 元素批量修改
bad
<ul id="box">

</ui>
<script>
    for(let i = 0;i<5;i++){
      let newLi = document.createElement('li')
      newLi.innerHTML=i
      box.appendChild(newLi)
    }
</script>

better
let frg =document.createDocumentFragment()
fo(let i = 0;i<5;i++){
    let newLi = document.createElement('li')
    newLi.innerHTML=i
    frg.appendChild(newLi)
}
box.appendChild(frg)
frg = null

best
// 字符串拼接
let str = ``;
for (let i = 0; i < 5; i++) {
	str += `<li>${i}</li>`;
}
box.innerHTML = str;
  • 动画效果应用到position属性为absolute或fixed的元素上(脱离文档流)
box.style.transform='translateX(200x)'
  • CSS3硬件加速(GPU加速)
    比起考虑如何减少回流重绘,我们更期望的是,根本不要回流重绘;transform \ opacity \ filters ... 这些属性会触发硬件加速,不会引发回流和重绘......

    可能会引发的坑:过多使用会占用大量内存,性能消耗严重、有时候会导致字体模糊等

  • 牺牲平滑度换取速度
    每次1像素移动一个动画,但是如果此动画使用了100%的CPU,动画就会看上去是跳动的,因为浏览器正在与更新回流做斗争。每次移动3像素可能看起来平滑度低了,但它不会导致CPU在较慢的机器中抖动

  • 避免table布局和使用css的javascript表达式

  • 尽量少使用dispaly:none,可以使用visibility:hidden代替,dispaly:none会造成重排,visibility:hidden会造成重绘。

  • 使用resize事件时,做防抖和节流处理。

  • 批量修改元素时,可以先让元素脱离文档流,等修改完毕后,再放入文档流。

ref
juejin.cn/post/684490…
juejin.cn/post/684490…