详解浏览器重绘和重排

文章目录

  • 浏览器是如何把URL 变成一个屏幕上显示的网页的?
    • 1. 构建Dom 树
    • 2. 把css 属性规则应用到节点上,给Dom树添加上css 属性
    • 3. 浏览器确定元素的位置(排版)
    • 4. 根据样式信息和大小信息,为元素在内存中进行渲染,并绘制到响应的位置
      • 渲染(render)
      • 合成(compositing)
      • 绘制
  • 重绘和重排
    • 重排(reflow)
      • 常见引起重排的方法
      • 重排影响的范围
    • 重绘(repaint)
      • 常见引起重绘的属性:
      • 浏览器的渲染队列
    • 减少重绘重排的操作
      • 1. 分离读写操作
      • 2. 样式集中改变(使用className)
      • 3. 缓存布局信息(使用变量储存计算样式)
      • 4. 离线改变dom

首先提一个我之前积累过得问题

浏览器是如何把URL 变成一个屏幕上显示的网页的?

  1. 浏览器首先使用http 协议或者https 协议,向服务器请求页面
  2. 把请求回来的HTML 代码经过解析,构建成DOM 树
  3. 计算dom 树上的css 属性,得到CSSDOM树
  4. 最后根据css属性对元素逐个进行渲染,得到内存中的位图(点阵图,是由像素的单个点组成的,这些点可以进行不同的排列和染色构成图样)【结合DOM树和CSSDOM树,得到渲染树】
  5. 一个可选的步骤是对位图进行合成,这会极大的增加后续绘制的速度【将渲染树的是所有节点进行位图合成,生成布局flow】
  6. 合成之后,再绘制【paint】到界面上

详解浏览器重绘和重排_第1张图片

如果上图不能理解的话,就看下面这个图
详解浏览器重绘和重排_第2张图片
需要知道的是:
从http请求回来开始,这个过程就并非一般想象中的一步做完再做下一步,而是一条流水线

从http请求回来,就产生了流式的数据,后续的DOM 树构建,css计算,渲染,合成,绘制,都是尽可能的流式的处理前一步的产出;即不需要等到上一步完全结束,就直接开始上一步的输出,这样我们在浏览网页时,才会看到逐步出现的页面。

1. 构建Dom 树

我觉得这个部分比较像编译原理(通过词法分析,语法分析进行构建)
详解浏览器重绘和重排_第3张图片

这颗构建的Dom 树,实际上信息不是全的,只有节点和属性,不包括任何的样式和信息
构建Dom 的过程是:从父到子,从先到后,一个一个节点构造,并且挂载到Dom 树上

2. 把css 属性规则应用到节点上,给Dom树添加上css 属性

在构建Dom 树时,同步计算了css 属性

我们依次拿到上一步构建好的元素,去检查他匹配了哪些规则,再根据规则的优先级,进行覆盖和调整
选择器的出现顺序,必定跟构建Dom 树的顺序一致。这是css设计的原则,即保证选择器在Dom 树构建到当前节点时,已经可以准确的判断是否可以匹配,而不需要后续节点的信息

3. 浏览器确定元素的位置(排版)

可分为

  • 文字排版
  • 盒排版
    • 正常流排版
    • 绝对定位排版
    • 浮动元素排版
    • 其他排版
      • flex 排版

4. 根据样式信息和大小信息,为元素在内存中进行渲染,并绘制到响应的位置

渲染(render)

可分为

  • 图形
  • 文字

渲染过程(特指我这里讲的)是不会把子元素绘制到渲染的位图上去,这样,当父子元素的相对位置发生变化时,可以保证渲染的结果能够最大程度化被缓存,减少重新渲染

合成(compositing)

上一小节中讲到,渲染过程不会把子元素渲染到位图上面,合成的过程:就是为一些元素创建一个“合成后的位图”(合成层),把一部分子元素渲染到合成的位图上面

目前主流浏览器一般根据position transform 等属性来决定合成策略,来“猜测”这些元素在未来可能发生变化

绘制

绘制是把“位图最终绘制到屏幕上,变成肉眼可见的图像”
我们把任何位图合成到这个“最终位图”的操作称为绘制
一般来说浏览器并不需要用代码来处理这个过程,浏览器只需要把最终的要显示的位图交给操作系统即可

在上面两个小节中,已经得到了每个元素的位图,并且对他们部分进行了合成,绘制过程就是按照z-index 把他们一次绘制到屏幕上


重绘和重排

  • 重绘:某些元素的外观被改变,例如:元素的填充颜色
  • 重排:重新生成布局,重新排列元素

仔细理解概念:单单改变元素的外观,肯定不会引起网页重新生成布局。但是浏览器完成重排之后,重绘将会受到重排的影响。

"重绘"不一定会出现"重排","重排"必然会出现"重绘"

重排(reflow)

DOM对象的位置和尺寸大小改变时,浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置,这个过程叫做重排。

回流
回流就好比向河里(文档流)扔了一块石头(dom变化),激起涟漪,然后引起周边水流受到波及,所以叫做回流

常见引起重排的方法

任何会改变元素几何信息(元素的位置和尺寸大小)的操作,都会触发重排

  1. 添加或删除可见的Dom 元素
  2. 元素尺寸改变–边距、填充、边框、宽度和高度
  3. 内容变化,比如用户在input框中输入文字
  4. 浏览器窗口改变–resize事件发生时
  5. 计算offsetWidth 和 offsetHeight 属性
  6. 设置 style 属性的值

重排影响的范围

由于浏览器渲染界面是基于流失布局模型的,所以触发重排时会对周围DOM重新排列,影响的范围有两种:

  • 全局范围:从根节点html开始对整个渲染树进行重新布局。
  • 局部范围:对渲染树的某部分或某一个渲染对象进行重新布局
    用局部布局来解释这种现象:把一个dom的宽高之类的几何信息定死,然后在dom内部触发重排,就只会重新渲染该dom内部的元素,而不会影响到外界。

重绘(repaint)

当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘。

常见引起重绘的属性:

详解浏览器重绘和重排_第4张图片

浏览器的渲染队列

div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';

根据我们上文的定义,这段代码理论上会触发4次重排+重绘,因为每一次都改变了元素的几何属性,实际上最后只触发了一次重排,这都得益于浏览器的渲染队列机制:

当我们修改了元素的几何属性,导致浏览器触发重排或重绘时。它会把该操作放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器就会批量执行这些操作。

强制刷新队列

div.style.left = '10px';
console.log(div.offsetLeft);
div.style.top = '10px';
console.log(div.offsetTop);
div.style.width = '20px';
console.log(div.offsetWidth);
div.style.height = '20px';
console.log(div.offsetHeight);

这段代码会触发4次重排+重绘,因为在console中你请求的这几个样式信息,无论何时浏览器都会立即执行渲染队列的任务,即使该值与你操作中修改的值没关联。

因为队列中,可能会有影响到这些值的操作,为了给我们最精确的值,浏览器会立即重排+重绘。
强制刷新队列的style样式请求:

offsetTop, offsetLeft, offsetWidth, offsetHeight
scrollTop, scrollLeft, scrollWidth, scrollHeight
clientTop, clientLeft, clientWidth, clientHeight
getComputedStyle(), 或者 IE的 currentStyle

我们在开发中,应该谨慎的使用这些style请求,注意上下文关系,避免一行代码一个重排,这对性能是个巨大的消耗

减少重绘重排的操作

1. 分离读写操作

div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';
console.log(div.offsetLeft);
console.log(div.offsetTop);
console.log(div.offsetWidth);
console.log(div.offsetHeight);

还是上面触发4次重排+重绘的代码,这次只触发了一次重排:
在第一个console的时候,浏览器把之前上面四个写操作的渲染队列都给清空了。剩下的console,因为渲染队列本来就是空的,所以并没有触发重排,仅仅拿值而已。

2. 样式集中改变(使用className)

div.style.left = '10px';
div.style.top = '10px';
div.style.width = '20px';
div.style.height = '20px';

虽然现在大部分浏览器有渲染队列优化,不排除有些浏览器以及老版本的浏览器效率仍然低下:

建议通过改变class或者csstext属性集中改变样式

// bad
var left = 10;
var top = 10;
el.style.left = left + "px";
el.style.top  = top  + "px";
// good 
el.className += " theclassname";
// good
el.style.cssText += "; left: " + left + "px; top: " + top + "px;";

3. 缓存布局信息(使用变量储存计算样式)

// bad 强制刷新 触发两次重排
div.style.left = div.offsetLeft + 1 + 'px';
div.style.top = div.offsetTop + 1 + 'px';

// good 缓存布局信息 相当于读写分离
var curLeft = div.offsetLeft;
var curTop = div.offsetTop;
div.style.left = curLeft + 1 + 'px';
div.style.top = curTop + 1 + 'px';

4. 离线改变dom

  1. dom 改变之前可设置先隐藏
    在要操作dom之前,通过display隐藏dom,当操作完成之后,才将元素的display属性为可见,因为不可见的元素不会触发重排和重绘。
dom.display = 'none'
// 修改dom样式
dom.display = 'block'
  1. 通过使用DocumentFragment创建一个dom碎片,在它上面批量操作dom,操作完成之后,再添加到文档中,这样只会触发一次重排。
  2. 复制节点,在副本上工作,然后替换它!

参考文章:https://juejin.im/post/5c15f797f265da61141c7f86

你可能感兴趣的:(HTML,重绘与重排)