带你从新认识浏览器的渲染机制——重绘重排

性能优化中,减小重绘重排应该是一种很好的优化方式,咱们具体看一下什么状况下会形成重绘重排,为何减小重绘重排能够作到优化,怎么样减小重绘重排。css

浏览器渲染过程

咱们先看看当浏览器拿到服务端返回的资源时,是如何渲染的。html

首先浏览器会进行文件解析,主要解析三个东西:jquery

  1. 解析 html/xhtml/svg,造成 dom 树。
  2. 解析 css,产生 CSS Rule Tree。
  3. 解析 js,js 会经过 api(包括 DOM API 和 CSSOM API) 来操做 Dom Tree 和 CSS Rule Tree。

解析完成以后api

  1. 经过 CSS Rule Tree 和 Dom Tree 生成 rendering Tree。其中不包括 display:none 的元素。
  2. 计算 DOM 节点的位置,对元素进行分层及布局,也叫 reflow 和 layout 过程。

布局完成以后,就要进行绘制了,将各层发给 GPU,GPU 将各层合成,显示在屏幕上,即 composite。浏览器

什么状况下会形成重绘重排

当咱们开始绘制的时候,若是使用 js 操做了 dom 元素,或者改变了 css 属性,就可能会形成重绘(repaint)和重排(reflow)。性能优化

repaint:屏幕的一部分进行了重画,好比某个 css 中改变背景色,元素尺寸没有变。 reflow:任何一个元素的尺寸发生了变化,须要从新验证并计算 render tree,就会形成重排。app

在 PC 时代,咱们用 jquery 进行获取元素,改变元素的尺寸,及时发生重排,咱们也很难感知到,可是当移动时代到来以后,若是频繁发生重排,那手机就会受不了了。dom

尤为是在执行下面操做时,成本会很高:svg

  1. js 添加或者删除元素的时候
  2. js 改变元素的位置发生改变时
  3. 页面初始化
  4. 容器尺寸发生变化。好比在标准盒模型下添加 padding,border 就会形成重排,因此在书写样式的时候,不少会将盒模型设计成怪异盒模型(box-sizing: border-box)
  5. js 获取元素的尺寸单位。
    • offsetTop, offsetLeft, offsetWidth, offsetHeight
    • scrollTop/Left/Width/Height
    • clientTop/Left/Width/Height
    • IE 中的 getComputedStyle(), 或 currentStyle

若是发生上述的行为基本都会形成重绘和重排。工具

当发生重排时,必定会发生重绘,可是发生重绘不必定会发生重排。

浏览器中每一个元素节点都有 reflow 方法,当一个元素发生 reflow 时,他的子节点都会发生 reflow。

举几个例子来讲明一下形成重绘重排的状况:

var bodyStyle = document.body.style; // cache

bodyStyle.padding = '20px'; // reflow, repaint
bodyStyle.border = '10px solid red'; // 再一次的 reflow 和 repaint

bodyStyle.color = 'blue'; // repaint
bodyStyle.backgroundColor = '#fad'; // repaint

bodyStyle.fontSize = '2em'; // reflow, repaint

// new DOM element - reflow, repaint
document.body.appendChild(document.createTextNode('children!'));
复制代码

为何减小重绘重排能够优化性能

前面说了浏览器的渲染机制,多一次重绘就须要浏览器从新进行一次绘制,及时 GPU 处理会比较快,可是也是吃不消的,更别说重排了,重排一个 dom,会从新生成 render Tree,而后从新绘制。

如何减小重绘重排

其实浏览器很聪明,不可能每次修改样式就 reflow 或者 repaint 一次,通常来讲,浏览器会积累一批操做,而后作一次 reflow。

可是也有些例外状况,好比 resize 窗口,改变窗口字体,浏览器会当即进行 reflow。

虽然浏览器会这么作,可是咱们也应该减小重绘重排的次数,在开发阶段就为浏览器进行特殊的关爱,毕竟是天天陪伴咱们的小伙伴。

下面总结了一些针对 reflow 和 repaint 的最佳实践:

  1. 不要一条一条的修改 DOM 样式,尽可能提早设置好 class,后续增长 class,进行批量修改。
  2. 把 DOM 离线后修改。
    • 使用 documentFragment 对象在内存里操做 DOM。
    • 使用 requestAnimationFrame 能够进行优化,在下一帧进行操做。
    • 把修改频繁的元素先 display: none,修改完以后显示,修改个 100 次也无妨。
    • clone 一个 dom 节点在内存里,修改以后;与在线的节点相替换。
  3. 不要把 DOM 元素的属性值放到一个循环中当成循环的变量。
  4. 尽量的修改低层级的节点,避免修改高层级的节点,形成大面积的 reflow。
  5. 千万不要使用 table 布局,修改一小块地方,会形成整个 table 从新布局。
  6. 动画尽可能使用 css 动画,css 动画中尽可能只使用 transform 和 opacity,这不会发生重排和重绘

composite

当每次布局完成以后,就会发生 composite 过程,浏览器都把重绘后的图像发给 GPU 去合成并显示。

在上面最佳实践中最后提到了动画,动画实际上是比较耗费性能的,由于动画的每一帧都会发给 GPU 去合成,重绘重排会发生在动画的每一帧。

咱们在写动画的时候,能够经过 js 写,也能够经过 css 写。两种方式在写动画时,过程也是不同的。

  1. js 写的动画,过程:js 计算 -> 重排(若布局改变) -> 重绘 -> 合成
  2. css 动画,过程:重排(若布局改变)-> 重绘 -> 合成

因此不难看出,耗费性能最少并能并最流畅的动画是只触发合成。

为了仅发生 composite,咱们作动画的 css property 必须知足如下三个条件:

  1. 不影响文档流。
  2. 不依赖文档流。
  3. 不会形成重绘。

知足以上以上条件的 css property 只有 transform 和 opacity。

这样的话,因为没有重排和重绘,只有合成,那么浏览器在动画执行以前就知道动画如何开始和结束。

而且有两个优点:

  1. 动画将会很是流畅
  2. 动画不在绑定到 CPU,即便 js 执行大量的工做;动画依然流畅

事实上影响动画流畅性的因素不止重排重绘,还有 CPU 内存。

css 动画有一个重要的特性,它是彻底工做在 GPU 上。由于你声明了一个动画如何开始和如何结束,浏览器会在动画开始前准备好全部须要的指令;并把它们发送给 GPU。

而若是使用 js 动画,浏览器必须计算每一帧的状态;为了保证平滑的动画,咱们必须在浏览器主线程计算新状态;把它们发送给 GPU 至少 60 次每秒。

除了计算和发送数据比 css 动画要慢,主线程的负载也会影响动画; 当主线程的计算任务过多时,会形成动画的延迟、卡顿。

因此最佳实践中最后一条就提到了,在写动画时,尽可能写 css 动画,而且尽可能用 transform 和 opacity。

辅助工具

谷歌浏览器检测重绘工具:右上角...,more tools,rendering。

勾选了 paint flashing 时,在刷新页面以后,就会发现有有绿色的背景,有绿色的背景就表明着重排。

还能够勾选 FPS meter,能够查看渲染页面时候 GPU 的使用率。

方便发现本身在开发过程当中,哪些操做形成了重绘重排,尽可能减小修改次数,优化性能。

相关文章
相关标签/搜索