05-4 回流/重绘+分层合成机制

Posted by CodingWithAlice on April 9, 2021

05-4 回流/重绘+分层合成机制

配合博文一起看:05 渲染流程:HTML、CSS和JavaScript是如何变成页面的

参考另一篇博文:回流/重绘

帧 + 帧率

  • 显示器怎么显示图像?

    每个显示器都有自己的刷新频率,一般是60HZ(每秒更新60张图片 – 来源于显卡中的 前缓冲区),将读取的图像显示到显示器上。

  • 显卡的作用?

    显卡的作用是 合成新的图像,并将图像保存到 后缓冲区 – 一旦合成的图像写到后缓冲区,系统就让前/后缓冲区互换 –> 为了保证显示器能够读取到最新显卡合成的图像

  • 动画效果怎么实现?

    在滚动鼠标或者缩放页面时,渲染引擎 会通过 渲染流水线 生成新的图片,并发送到显卡的后缓冲区。【如果要保持动画流畅,渲染引擎需要每秒更新60张图片到显卡的后缓冲区】

  • 帧?

    把渲染流水线生成的每一幅图片为一帧

  • 帧率?

    把渲染流水线每秒更新了多少帧称为帧率

    举例:滚动过程中1秒更新了60帧,那么帧率就是 60HZ/60FPS

渲染引擎为了不卡顿做了什么? - 分层和合成机制

如何生成一幅图像

关于其中任意一帧的生成方式,有 重排、重绘和合成 三种方式,每种方式的渲染路径是不同的。

preview

看个例子 - 点击按钮,一个盒子移动位置,每一帧要进行的处理:

可以在 chromeperformance 里面看到事件的循环

image-20210709114925454

每一个循环拆解如下图所示:

image-20210709115011262

Schedule Style Recalculation(样式计算) -> Recalculate Style(样式计算) -> Update Layer Tree(回流/重排) -> Paint(重绘) -> Composite Layers(渲染层合并)

  • 1、JS 实现动画效果

    JavaScript 实现动画效果,DOM元素操作等。

  • 2、Style: 样式计算 - Schedule Style RecalculationRecalculate Style

    确定每个 DOM 元素应该应用什么 CSS 规则

  • 3、Layout: 回流/重排 reflow - Update Layer Tree

    • 定义:根据 CSSOM + DOM 计算每个 DOM 元素在最终屏幕上显示的大小和位置 - 即 计算布局 –> 整个渲染流水线(从 html 开始递归往下计算尺寸和位置开始)的每个阶段都要执行一遍;

    • 触发事件

      页面 初始渲染,这是开销最大的一次重排;

      添加/删除 可见的DOM元素;

      改变元素 位置

      改变元素 尺寸,比如边距、填充、边框、宽度和高度等;

      改变元素 内容,比如文字数量,图片大小等;

      改变元素 字体大小

      改变浏览器 窗口尺寸,比如 resize 事件发生时;

      激活CSS伪类(例如::hover);

      设置 style 属性的值,因为通过设置style属性改变结点样式的话,每一次设置都会触发一次reflow

      查询某些属性或调用某些计算方法:`offsetTop`, `offsetLeft`, `offsetWidth`, `offsetHeight`;`scrollTop`/Left/Width/Height;`clientTop`/Left/Width/Height;`width`,`height`;调用了`getComputedStyle()`, 或者 IE的 `currentStyle()` 等,原理是一样的,都为求一个“即时性”和“准确性”。

  • 4、Paint: 重绘 rePaint - Paint

    • 定义:元素的外观发生改变,但没有改变布局 —> 依然需要计算绘制信息(样式)
    • 触发事件:改变某个元素的 背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性 时,屏幕的一部分要重画,但是元素的几何尺寸没有变
  • 5、Composite: 渲染层合并:Composite Layers

    在每个层上完成绘制过程之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。

关于 display:nonevisibility:hidden

  • display:none 指的是元素完全不陈列出来,不占据空间,涉及到了DOM结构,故产生 回流+重绘
  • visibility:hidden 指的是元素不可见但存在,保留空间,不影响结构,故只产生 重绘
减少重排次数的几种方法:
  • 1、样式集中改变 - 更改类名 而不是修改样式
  • 2、分离读写操作 - 浏览器渲染机制: 重排/重绘操作被触发后会被放进渲染队列,等到队列中的操作到了一定的数量或者到了一定的时间间隔时,浏览器才会 *批量执行 *这些操作
  • 3、将 DOM 离线,修改完样式后再放出来
    • display:none,只会触发 隐藏+显示 两次重排
    • 创建新的 DOM 节点,操作完之后再添加到页面
    • 复制节点,操作完之后替换原节点
  • 4、脱离文档流(fixed/absolute) - 利用分层,只有当前层需要重排,代价开销小
  • 5、优化动画 - 利用 GPU 加速:Canvas2D,布局合成, CSS3转换(transitions),CSS3 3D变换(transforms),WebGL和视频(video)。使用css3硬件加速,可以让transform、opacity、filters、will-change 这些动画不会引起回流重绘 。但是对于动画的其它属性,比如background-color这些,还是会引起回流重绘的,不过它还是可以提升这些动画的性能

分层和合成流程

如果没有分层 –> 从布局树直接生成目标图片的话,每次很小的变化都会重排/重绘

分层 –> 将素材分解为多个图层的操作

合成 –> 最后将这些图层合并到一起的操作;

  • 分层、合成流程

    在 Chrome 的渲染流水线中,生成 布局树 之后

    –> 开始分层:渲染引擎会根据布局树的特点将其转换为 层树(Layer Tree)(层树是渲染流水线后续流程的基础结构)

    –> 层树中的 每个节点都对应着一个图层(下一步的绘制阶段就依赖于层树中的节点)

    –> 绘制阶段:将绘制指令组合成一个 绘制列表

    –> 光栅阶段:按照绘制列表中的指令生成图片,每一个图层都对应一张图片

    –> 合成线程 将这些图片合成为“一张”图片,并最终将生成的图片发送到 后缓冲区

合成操作是在 合成线程 上完成的,这也就意味着在执行合成操作时,是 不会影响到主线程执行 的。这就是为什么经常主线程卡住了,但是 CSS 动画依然能执行的原因

分块

合成线程 会将每个图层 分割为大小固定的图块,然后优先绘制靠近 视口 的图块,这样就可以大大加速页面的显示速度。

注意:还有一个优化点,首次合成图块的时候用一个低分辨率的图片显示,然后合成器继续绘制网页,完成后再替换低分辨率内容。

写代码的时候能做什么优化?

如果需要对某些元素做【几何形状变换、透明度变换或者一些缩放操作】,如果使用 JS 来实现这些效果,会涉及到整个渲染流水线。

  • 优化方案:利用下面这个样式的添加,渲染引擎会将该元素 单独实现一帧(不能随意增加,因为从渲染引擎准备一个独立层开始,后面的每一个阶段都会多一个层结构,都会占用额外的内存),等到这些变换发生时,渲染引擎会通过 合成线程 直接处理,就不会涉及到主线程 –> 这也就是为什么CSS动画比JS动画高效
    .box {
    will-change: opacitytransformtopleftbottomright;
    }
    

使用 transform 或者 opacity 来实现动画效果 - 不会触发 layout 和 paint, 这样只需要做合成层的合并就好了

对比一下 height: 200px -> height:100pxtransform:scale(.5) 的差异

image-20210711163012822