Web开发 之 回流和重绘

序言

回流重绘的了解,对Web开发者进一步了解页面渲染基本流程及页面性能优化都有帮助。本文将对这两步具体做了些什么,以及在整个页面渲染中的所在位置进行一些分享。

浏览器渲染过程了解

先从浏览器渲染过程来了解回流重绘,有一个整体的概念。
在这里插入图片描述
上图我们可以看到浏览器渲染过程如下:

  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 隐藏的节点, 还是会显示在渲染树上的。

渲染树只包含可见的节点

回流

回流: 通过构造渲染树,将可见DOM节点以及它对应的样式结合起来, 并计算它们在设备视口(viewport) 内的确切位置和大小的这个计算的阶段

为了获取每个对象在浏览器网页中确切大小和位置, 浏览器从渲染树的根节点开始遍历。
来看看如下例子:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>Critial Path</title>
  </head>
  <body>
    <div style="width: 50%">
      <div style="width: 50%">子盒子!</div>
    </div>
  </body>
</html>

如下图所示, 在回流这个阶段, 就需要根据视口具体的宽度,将其转为实际的像素。

在这里插入图片描述

重绘

通过构造渲染树和回流阶段, 浏览器页面已知道哪些节点是可见/不可见的样式和具体的集合信息(位置,大小)。 这时就可以将渲染树的每个节点转换为屏幕上的实际像素,这个阶段就叫重绘节点

知道浏览器渲染过程后, 我们下面来分享下何时会发生回流/重绘

回流/重绘触发条件

经过上面我们了解到,回流阶段主要是计算节点的位置和几何信息, 所以当页面布局和几何信息发生变化的时候, 就需要回流。 如下面的情况:

  • 添加或删除可见的DOM元素
  • 元素的位置发生变化
  • 元素的尺寸发生变化(如: 外边距,内边距,边框大小,高/宽度等)
  • 内容发生变化,比如文本变化或图片尺寸变化等
  • 页面一开始渲染的时候(这个可定避免不了)
  • 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)

通过上面我们应该知道: 回流一定会触发重绘,而重绘不一定会回流

*根据变化的范围和程度,渲染树中或大或小的部分需要重新计算, 有些改变会触发 * 整页面 的重排
比如: 滚动条出现或者修改了根节点

浏览器的优化机制

现在大多数浏览器都会通过队列化修改并批量执行来优化重排过程,进而避免每次重排都会造成额外的计算消耗。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列。
当获取布局信息操作的时候,会强制队列刷新, 如果是下方的操作之一:

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

上述所列属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。
因此,在修改样式的时候,最好避免使用上述属性。 如果要使用他们, 最好将值缓存起来

避免回流和重绘

下面我们来讨论下如何减少回流和重绘。

最小化重绘和重排

重绘和重排代价比较昂贵, 因此要减少它的发生次数。 可以考虑合并多次对DOM和样式的修改, 然后一次处理它们。
例:

const el = document.getElementById('box');
el.style.padding = '5px';
el.style.borderLeft = '1px';
el.style.borderRight = '2px';

代码中对DOM进行了三个样式属性的修改, 每个都会影响元素的几何结构,引起回流(目前大部分浏览器以优化,只会触发一次重排)。 在一些旧版浏览器/上面的代码执行时,会导致三次重排。

因此,可以合并所有的改变然后依次处理, 比如:

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

避免触发同步布局事件

来看看下面代码:

function init() {
    for (let i = 0; i < paragraphs.length; i++) {
        parent[i].style.width = box.offsetWidth + 'px';
    }
}

这段代码开起来没什么问题,但其会造成很大的性能问题。 每次循环的时候都读取了box的一个offsetWidth 属性值, 然后利用它来更新parent标签的width属性。每一次循环都会强制浏览器刷新队列
我们可以优化为:

const width = box.offsetWidth;
function init() {
    for (let i = 0; i < paragraphs.length; i++) {
        parent[i].style.width = width + 'px';
    }
}

复杂的动画效果,使用绝对定位让其脱离文档流

复杂的动画效果,会引起回流重绘。 因此, 我们可以使用绝对定位,让它脱离文档流。否则会引起父元素以及后续元素频繁的回流。

CSS3 硬件加速 (GPU 加速)

比起考虑减少回流重绘,不回流重绘也行是我们更想要的效果。 这个时候CSS3硬件加速就可以来聊聊了~

  1. 使用CSS3硬件加速,可以让transform, opacity, filters 这些动画不会引起回流重绘
  2. 对于动画的其它属性, 比如: background-color这些, 还是会引起回流重绘的, 不过它还是可以提升这些动画的性能

本章我们暂只考虑使用,先不聊原理…

如何使用
常见的触发硬件加速的CSS属性:

  • transform
  • opacity
  • filters
  • Will-change

CSS3 硬件加速的坑

  • 如果太多元素使用CSS3硬件加速, 会导致内存占用较大,会有性能问题。
  • 在GPU渲染字体会导致抗锯齿无效。 这是因为GPU和CPU的算法不同。 因此如果不在动画结束的时候关闭硬件加速,会产生字体模糊。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值