重绘(repaint)和重排(回流reflow)

262 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情

重绘(repaint)和重排(回流reflow)是什么?

重排

重排是指部分或整个渲染树需要重新分析,并且节点的尺⼨需要重新计算。

表现为 重新⽣成布局,重新排列元素。

重绘

重绘是由于节点的⼏何属性发⽣改变,或由于样式发⽣改变(例如:改变元素背景⾊)。

表现为某些元素的外观被改变。或者重排后, 进行重新绘制!

两者的关系

重绘不⼀定会出现重排,重排必定会触发重绘。

每个页面至少需要一次回流+重绘。(初始化渲染)

重排和重绘的代价都很⾼昂,频繁重排重绘, 会破坏⽤户体验、让界面显示变迟缓。

我们需要尽可能避免频繁触发重排和重绘, 尤其是重排

1. 何时会触发重排?

重排什么时候发生?

1、添加或者删除可见的DOM元素;

2、元素位置改变;

3、元素尺寸改变——边距、填充、边框、宽度和高度

4、内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;

5、页面渲染初始化;

6、浏览器窗口尺寸改变——resize事件发生时;

2. 浏览器对重绘重排的优化

思考下述代码的重绘重排过程!

let s = document.body.style
s.padding = "2px" // 重排 + 重绘
s.border = "1px solid red" // 再一次 重排 + 重绘
s.color = "blue" // 再一次重绘
s.backgroundColor = "#ccc" // 再一次 重绘
s.fontSize = "14px" // 再一次 重排 + 重绘
document.body.appendChild(document.createTextNode('abc!')) // 添加node,再一次 重排 + 重绘

聪明的浏览器:

从上个实例代码中可以看到几行简单的JS代码就引起了 4次重排、6次重绘。

而且我们也知道重排的花销也不小,如果每句JS操作都去重排重绘的话,浏览器可能就会受不了!

所以浏览器会优化这些操作,浏览器会维护1个队列,把所有会引起重排、重绘的操作放入这个队列

等队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理

这样就会让多次的重排、重绘变成了一次重排重绘。

虽然有了浏览器的优化,但有时候我们写的一些代码可能会强制浏览器提前flush队列,这样浏览器的优化可能起不到作用了。

比如当你请求向浏览器获取一些样式信息的时候(保证获取结果的准确性),就会让浏览器flush队列

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight
  2. scrollTop/Left/Width/Height
  3. clientTop/Left/Width/Height
  4. 请求了getComputedStyle()
  5. ....

猜一猜, 页面效果是什么:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    div {
      width: 200px;
      height: 200px;
      background-color: pink;
      transition: all 1s;
    }
​
  </style>
</head>
<body>
​
  <div id="box"></div>
​
  <script>
    const div = document.getElementById("box")
    // console.log(div.offsetWidth)
    div.style.width = '400px'
    div.style.height = '400px'
  </script>
  
</body>
</html>

3. 重绘重排角度, 我们应该如何优化页面渲染性能 ?

优化页面渲染性能的角度: 尽可能减少重绘和重排的次数

主要有几大方式来避免:

  • 1 集中修改样式 (这样可以尽可能利用浏览器的优化机制, 一次重排重绘就完成渲染)

  • 2 尽量避免在遍历循环中, 进行元素 offsetTop 等样式值的获取操作, 会强制浏览器刷新队列, 进行渲染

  • 3 利用 transform 实现动画变化效果, 去代替 left top 的变换 (轮播图等)

    transform变换, 只是视觉效果! 不会影响到其他盒子, 只触发了自己的重绘

  • 4 使用文档碎片(DocumentFragment)可以用于批量处理, 创建元素

文档碎片的理解:

documentFragment是一个保存多个元素的容器对象(保存在内存)当更新其中的一个或者多个元素时,页面不会更新。

当documentFragment容器中保存的所有元素操作完毕了, 只有将其插入到页面中才会更新页面。

<ul id="box"></ul>
​
<script>
  let ul = document.getElementById("box")
  for (let i = 0; i < 20; i++) {
    let li = document.createElement("li")
    li.innerHTML = "index: " + i
    ul.appendChild(li)
  }
​
​
  // let ul = document.getElementById("box")
  // let fragment = document.createDocumentFragment()
  // for (let i = 0; i < 20; i++) {
  //     let li = document.createElement("li")
  //     li.innerHTML = "index: " + i
  //     fragment.appendChild(li)
  // }
  // ul.appendChild(fragment)
</script>

vue底层渲染更新, 就用了 document.createDocumentFragment