页面重绘与回流

一.什么是页面的重绘与回流

浏览器在渲染一个页面的时候,从加载到完成,首先是构建DOM树,然后根据DOM节点的几何属性生成渲染树(不包括display:none,head节点但是会包括visibility:hidden节点),当渲染树构建完成,页面就根据DOM树开始布局了,渲染树也根据设置的样式对应的渲染这些节点。在这个过程中,回流与dom树和渲染树有关,重绘与渲染树有关。

比如我们删除一个dom节点,修改一个元素的宽高,这样就会导致页面的布局发生变化,DOM树的结构发生变化,引起dom树的重构,重构完成之后就会导致渲染树的重新渲染,这个过程就叫做回流

当我们修改一个元素的颜色,这并不会影响页面的布局,但是渲染树会重新渲染页面的样式颜色,这就是重绘

回流的代价是远远大于重绘的,回流一定导致重绘,但是重绘不一定导致回流。

二.常见的场景

回流常见于元素的尺寸,布局,隐藏等Dom结构发生改变的情况

  • 1.添加或者删除可见的dom元素
  • 2.元素位置改变
  • 3.元素尺寸改变(边距,填充,边框,高度和宽度)
  • 4.内容改变(内容物引起的元素大小发生变化)
  • 5.页面渲染初始化
  • 6.浏览器尺寸改变
  • 7.计算元素的偏移量属性(浏览器为了确保属性值的正确性会回流得到最新值,所以最好使用一个变量记录一下)

重绘常见于元素的颜色的样式发生改变的情况

  • 1.改变字体
  • 2.增加或者移除样式表
  • 3.内容变化(input输入框)
  • 4.激活CSS伪类
  • 5.设置style属性值
  • 6.计算offsetWidth和offsetHeight属性

三.如何优化浏览器的回流与重绘

1.将那些改变样式的操作集合在一次完事,直接改变className或者cssText

  • 使用cssText
    1
    2
    const el = document.getElementById('test'); 
    el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';
  • 修改CSS的class
    1
    2
    const el = document.getElementById('test'); 
    el.className += ' active';

    2.让要操作的元素进行离线处理,处理完事以后再一起更新

  • (1)使用DocumentFragment进行缓存操作,引发一次回流和重绘
  • (2)使用display:none,只引发两次回流和重绘。道理跟上面的一样。因为display:none的元素不会出现在render树
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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';
  • (3)使用cloneNode和replaceChild技术,引发一次回流和重绘(将原始元素拷贝到一个脱离文档流的节点中,修改节点之后,再替换原始元素)
    1
    2
    3
    4
    const ul = document.getElementById('list');
    const clone = ul.cloneNode(true);
    appendDataToElement(clone, data);
    ul.parentNode.replaceChild(clone, ul);

    3.不要经常访问会引起浏览器flush队列的属性,非要高频访问的话建议缓存到变量;

    4.将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。例如有动画效果的元素就最好设置为绝对定位;

    5.尽量不要使用表格布局,如果没有定宽,表格一列的宽度由最宽的一列决定,那么很可能在最后一行的宽度超出之前的列宽,引起整体回流造成table可能需要多次计算才能确定好其在渲染树中节点的属性,通常要花3倍于同等元素的时间。

    6.避免触发同步布局事件

    现代浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列。但是!当你获取布局信息的操作的时候,会强制队列刷新,比如当你访问以下属性或者使用以下方法:
  • offsetTop、offsetLeft、offsetWidth、offsetHeight
  • scrollTop、scrollLeft、scrollWidth、scrollHeight
  • clientTop、clientLeft、clientWidth、clientHeight
  • getComputedStyle()
  • getBoundingClientRect
    1
    2
    3
    4
    5
    function initP() {
    for (let i = 0; i < paragraphs.length; i++) {
    paragraphs[i].style.width = box.offsetWidth + 'px';
    }
    }
    改为
    1
    2
    3
    4
    5
    6
    const width = box.offsetWidth;
    function initP() {
    for (let i = 0; i < paragraphs.length; i++) {
    paragraphs[i].style.width = width + 'px';
    }
    }
    以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,最好避免使用上面列出的属性,他们都会刷新渲染队列。如果要使用它们,最好将值缓存起来

    7.css3硬件加速,使用部分css3的属性不会引发页面的回流与重绘或者造成的影响比较小

    四.浏览器渲染的过程

upload successful
渲染过程大致如下:
1.解析HTML,生成DOM树,解析CSS,生成CSSOM树
2.将DOM树和CSSOM树结合,生成渲染树
3.回流(Layout):根据生成的渲染树,进行回流得到节点信息(位置,大小)
4.重绘(Painting):根据渲染树以及回流得到的几何信息,得到节点的绝对像素
5.Display:将像素发送给GPU,展示在页面上
生成渲染树

upload successful

  • 1.从DOM树的根节点开始遍历每个可见节点。
  • 2.对于每个可见的节点,找到CSSOM树中对应的规则,并应用它们。
  • 3.根据每个可见节点以及其对应的样式,组合生成渲染树。
  • *不可见的节点:**(渲染树只包含可见的节点)
  • 一些不会被渲染出来的点,比如:script,meta,link等
  • 一些通过css进行隐藏的节点。比如display:none。注意,利用visibility和opacity隐藏的节点,还是会显示在渲染树上的。只有display:none的节点才不会显示在渲染树上。

学习链接:

1.https://www.cnblogs.com/dujingjie/p/5784890.html

2.https://www.cnblogs.com/wanan-01/p/7732340.html

3.https://zhuanlan.zhihu.com/p/22181897

4.https://zhuanlan.zhihu.com/p/52076790


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!