什么是重排与重绘?

参考文章
网页性能管理详解 ——阮一峰

1. 网页生成的过程

网页生成的过程,大致可以分为五步:

  1. HTML 代码转换为 DOM
  2. CSS 代码转换为 CSSOM ( CSS Object Model )
  3. 结合 DOMCSSOM,生成一颗渲染树 ( 包括每个节点的视觉信息 )
  4. 生成布局 ( layout ), 即将所有渲染树的所有节点进行平面合成
  5. 布局绘制 ( paint ) 到屏幕上

2. 重排和重绘

2.1 重排

渲染树中部分或者全部元素的尺寸结构或者属性发生变化时,浏览器就会重新渲染部分或者全部文档,这个过程就叫重排。每个页面最少发生一次重排,那就是在页面第一次加载的时候。

2.2 重排的触发条件

  • 页面的第一次加载 ( 必定发生 )
  • 浏览器的窗口大小发生变化
  • 元素的内容发生变化
  • 元素的尺寸、位置发生变化
  • 元素的字体大小发生变化
  • 激活 CSS 伪类 ( 例如 :hover )
  • 查询某些属性或者调用某些方法
  • 添加或者删除可见的 DOM 元素

2.3 重绘

当页面中某些元素的样式发生改变,但是不会影响其在文档流中的位置时,浏览器就会对该元素进行重新绘制,这个过程就是重绘

2.4 重绘的触发条件

  • color、background 相关属性
  • outline 相关属性
  • border-radius、visibility、box-shadow
  • 重排 ( 重绘不一定引起重排,但是重排一定会重绘 )

3. 怎么避免重排与重绘

  • 避免使用层级较深的选择器,或其他一些复杂的选择器
  • 设置动画效果时,使用 absolute 或者 fixed,使元素脱离文档流
  • 避免使用 calc() 表达式,因为 CSS 表达式不仅是在页面呈现和调整大小时计算,而且在页面滚动时、鼠标移动时都要重新进行计算
  • 多个 DOM 元素的读写操作,应分类存放,不要两个读操作直接加入一个写操作
  • 避免频繁操作样式,最好将样式一次性改变,直接改变 className 或者使用 cssText 属性
  • 避免频繁操作 DOM,创建一个 documentFragment,在它上面应用所有的 DOM 操作,最后再把它添加到文档
  • 可以先把元素设置为 display:none, 操作结束后再将其显示出来。因为在 display:none 的元素上进行的 DOM 操作不会引起来重排和重绘

3.1 浏览器的优化机制

浏览器针对页面的重排与重绘,进行了自身的优化:渲染队列

浏览器会将所有的重排与重绘操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批量处理,这样就会让多次的重排、重绘变成一次重排重绘。

但是,当获取布局信息的操作时,会强制队列刷新,比如以下属性和方法:

offsetTop、 offsetLeft、 offsetWidth、 offsetHeight
scrollTop、 scrollLeft、 scrollWidth、 scrollHeight
clientTop、 clientLeft、 clientWidth、 clientHeight
getComputedStyle()
getBoundingClientRect

// 这些操作都需要返回最新的布局信息,因此浏览器不得不清空队列,触发重绘来返回正确的值。

4. documentFragment 是什么?

DocumentFragment 是一个保存多个 element 的容器对象 ( 保存在内存 ),当更新其中一个或者多个 element 时,页面不会更新。只有当 DocumentFragment 容器中保存的所有 element 更新后再插入到页面中才会更新页面。

由于 DocumentFragment 不会出现在文档树中,将 DocumentFragment 插入文档树中,相当于把它的子孙节点插入到文档树中,在频繁的 DOM 操作时,可以将 DOM 元素插入到 DocumentFragment 中处理,然后一次性的将所有的子孙节点插入到文档中。和直接操作 DOM 相比,将 DocumentFragment 节点插入到 DOM 树中,仅仅会触发一次页面的重绘,这样大大的提高了页面的性能

<ul id="list"></ul>
let ul = document.querySelector('#list')
let flag = document.createDocumentFragment()

for (let i = 0; i < 100; i++) {
    let li = document.createElement('li')
    li.innerHTML = i
    flag.appendChild(li)
}

ul.appendChild(flag)
posted @ 2022-10-27 22:44  如是。  阅读(427)  评论(0编辑  收藏  举报