浏览器渲染流程&Composite(渲染层合并)简单总结

梳理浏览器渲染流程

首先简单了解一下浏览器请求、加载、渲染一个页面的大体过程:css

  • DNS 查询
  • TCP 链接
  • HTTP 请求即响应
  • 服务器响应
  • 客户端渲染

这里主要将客户端渲染展开梳理一下,从浏览器器内核拿到内容(渲染线程接收请求,加载网页并渲染网页),渲染大概能够划分红如下几个步骤:html

  • 解析html创建dom树
  • 解析css构建render树(将CSS代码解析成树形的数据结构,而后结合DOM合并成render树)
  • 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
  • 绘制render树(paint),绘制页面像素信息
  • 浏览器会将各层的信息发送给GPU(GPU进程:最多一个,用于3D绘制等),GPU会将各层合成(composite),显示在屏幕上。

参考一张图(webkit渲染主要流程):前端

图片描述

这里先解释一下几个概念,方便你们理解:html5

  DOM Tree:浏览器将HTML解析成树形的数据结构。git

  CSS Rule Tree:浏览器将CSS解析成树形的数据结构。github

  Render Tree: DOM和CSSOM合并后生成Render Tree。web

  layout: 有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系,从而去计算出每一个节点在屏幕中的位置。chrome

  painting: 按照算出来的规则,经过显卡,把内容画到屏幕上。canvas

  reflow(回流):当浏览器发现某个部分发生了点变化影响了布局,须要倒回去从新渲染,内行称这个回退的过程叫 reflow。reflow 会从 <html> 这个 root frame 开始递归往下,依次计算全部的结点几何尺寸和位置。reflow 几乎是没法避免的。如今界面上流行的一些效果,好比树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引发浏览器的 reflow。鼠标滑过、点击……只要这些行为引发了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引发它内部、周围甚至整个页面的从新渲 染。一般咱们都没法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。api

  repaint(重绘):改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,可是元素的几何尺寸没有变。

注意:

  1. display:none 的节点不会被加入Render Tree,而visibility: hidden
    则会,因此,若是某个节点最开始是不显示的,设为display:none是更优的。
  2. display:none 会触发 reflow,而 visibility:hidden 只会触发 repaint,由于没有发现位置变化。
  3. 有些状况下,好比修改了元素的样式,浏览器并不会马上reflow 或 repaint 一次,而是会把这样的操做积攒一批,而后作一次reflow,这又叫异步 reflow 或增量异步 reflow。可是在有些状况下,好比resize窗口,改变了页面默认的字体等。对于这些操做,浏览器会立刻进行 reflow。

再参考一张图理解一下:

图片描述

细致分离两个环节,其余环节参考上述概念注解:

JavaScript:JavaScript实现动画效果,DOM元素操做等。
Composite(渲染层合并):对页面中 DOM 元素的绘制是在多个层上进行的。在每一个层上完成绘制过程以后,浏览器会将全部层按照合理的顺序合并成一个图层,而后显示在屏幕上。对于有位置重叠的元素的页面,这个过程尤为重要,由于一旦图层的合并顺序出错,将会致使元素显示异常。

在实际场景下,大体会出现三种常见的渲染流程(Layout和Paint步骤是可避免的,可参考上一张图的注意部分理解):

图片描述


Composite

了解层

注意:首先说明,这里讨论的是 WebKit,描述的是 Chrome 的实现细节,而并不是是 web 平台的功能,所以这里介绍的内容不必定适用于其余浏览器。

  • Chrome 拥有两套不一样的渲染路径(rendering path):硬件加速路径和旧软件路径(older software path)
  • Chrome 中有不一样类型的层: RenderLayer(负责 DOM 子树)和GraphicsLayer(负责 RenderLayer的子树),只有 GraphicsLayer 是做为纹理(texture)上传给GPU的。
  • 什么是纹理?能够把它想象成一个从主存储器(例如 RAM)移动到图像存储器(例如 GPU 中的 VRAM)的位图图像(bitmapimage)
  • Chrome 使用纹理来从 GPU上得到大块的页面内容。经过将纹理应用到一个很是简单的矩形网格就能很容易匹配不一样的位置(position)和变形(transformation)。这也就是3DCSS 的工做原理,它对于快速滚动也十分有效。

整个图:

图片描述

在 Chrome 中其实有几种不一样的层类型:

  • RenderLayers 渲染层,这是负责对应 DOM 子树
  • GraphicsLayers 图形层,这是负责对应 RenderLayers子树。

在浏览器渲染流程中提到了composite概念,在 DOM 树中每一个节点都会对应一个 LayoutObject,当他们的 LayoutObject 处于相同的坐标空间时,就会造成一个 RenderLayers ,也就是渲染层。RenderLayers 来保证页面元素以正确的顺序合成,这时候就会出现层合成(composite),从而正确处理透明元素和重叠元素的显示。

某些特殊的渲染层会被认为是合成层(Compositing Layers),合成层拥有单独的 GraphicsLayer,而其余不是合成层的渲染层,则和其第一个拥有 GraphicsLayer 父层公用一个。

而每一个GraphicsLayer(合成层单独拥有的图层) 都有一个 GraphicsContext,GraphicsContext 会负责输出该层的位图,位图是存储在共享内存中,做为纹理上传到 GPU 中,最后由 GPU 将多个位图进行合成,而后显示到屏幕上。

如何变成合成层

合成层建立标准

什么状况下能使元素得到本身的层?虽然 Chrome的启发式方法(heuristic)随着时间在不断发展进步,可是从目前来讲,知足如下任意状况便会建立层:

  • 3D 或透视变换(perspective transform) CSS 属性
  • 使用加速视频解码的 <video> 元素 拥有 3D
  • (WebGL) 上下文或加速的 2D 上下文的 <canvas> 元素
  • 混合插件(如 Flash)
  • 对本身的 opacity 作 CSS动画或使用一个动画变换的元素
  • 拥有加速 CSS 过滤器的元素
  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在本身的层里)
  • 元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

合成层的优势

淘宝的栗子举的很详细,值得一看,里面提到了一旦renderLayer提高为了合成层就会有本身的绘图上下文,而且会开启硬件加速,有利于性能提高,里面列举了一些特色

  • 合成层的位图,会交由 GPU 合成,比 CPU 处理要快
  • 当须要 repaint 时,只须要 repaint 自己,不会影响到其余的层
  • 对于 transform 和 opacity 效果,不会触发 layout 和 paint

注意:

  1. 提高到合成层后合成层的位图会交GPU处理,但请注意,仅仅只是合成的处理(把绘图上下文的位图输出进行组合)须要用到GPU,生成合成层的位图处理(绘图上下文的工做)是须要CPU。
  2. 当须要repaint的时候能够只repaint自己,不影响其余层,可是paint以前还有style, layout,那就意味着即便合成层只是repaint了本身,但style和layout自己就很占用时间。
  3. 仅仅是transform和opacity不会引起layout 和paint,那么其余的属性不肯定。

总结合成层的优点:通常一个元素开启硬件加速后会变成合成层,能够独立于普通文档流中,改动后能够避免整个页面重绘,提高性能。

性能优化点:

  1. 提高动画效果的元素 合成层的好处是不会影响到其余元素的绘制,所以,为了减小动画元素对其余元素的影响,从而减小paint,咱们须要把动画效果中的元素提高为合成层。 提高合成层的最好方式是使用 CSS 的 will-change属性。从上一节合成层产生缘由中,能够知道 will-change 设置为opacity、transform、top、left、bottom、right 能够将元素提高为合成层。
  2. 使用 transform 或者 opacity 来实现动画效果, 这样只须要作合成层的合并就行了。
  3. 减小绘制区域 对于不须要从新绘制的区域应尽可能避免绘制,以减小绘制区域,好比一个 fix 在页面顶部的固定不变的导航header,在页面内容某个区域 repaint 时,整个屏幕包括 fix 的 header 也会被重绘。而对于固定不变的区域,咱们指望其并不会被重绘,所以能够经过以前的方法,将其提高为独立的合成层。减小绘制区域,须要仔细分析页面,区分绘制区域,减小重绘区域甚至避免重绘。

利用合成层可能踩到的坑

  1. 合成层占用内存的问题
  2. 层爆炸,因为某些缘由可能致使产生大量不在预期内的合成层,虽然有浏览器的层压缩机制,可是也有不少没法进行压缩的状况,这就可能出现层爆炸的现象(简单理解就是,不少不须要提高为合成层的元素由于某些不当操做成为了合成层)。解决层爆炸的问题,最佳方案是打破 overlap 的条件,也就是说让其余元素不要和合成层元素重叠。简单直接的方式:使用3D硬件加速提高动画性能时,最好给元素增长一个z-index属性,人为干扰合成的排序,能够有效减小chrome建立没必要要的合成层,提高渲染性能,移动端优化效果尤其明显。 在这篇文章中的demo能够看出其中厉害。

用chremo打开demo页面后,开启浏览器的开发者模式,再按照如图操做打开查看工具:

图片描述

开启 Rendering 的Layer borders后 观察点击为动画元素设置z-index复选框的页面提示变化:

图片描述

上图中能够明显看出:页面中设置了一个h1标题,应用了translate3d动画,使得它被放到composited layer中渲染,而后在这个元素后面建立了2000个list。在不为h1元素设置z-index的状况下,使得本不须要提高到合成层的ul元素下的每一个li元素都提高为一个单独合成层(每一个li元素的黄色提示边框),最终会致使GPU资源过分消耗页面滑动时很卡,尤为在移动端(安卓)上更加明显。

图片描述

如上图操做选中为动画元素设置z-index,能够看出ul下的每一个li都回归到普通渲染层,再也不是合成层也就不会消耗GPU资源去渲染,从而达到了优化页面性能优化的目的。

你们能够用支持『硬件加速』的『安卓』手机浏览器测试上述页面,给动画元素加z-index先后的性能差距很是明显。

最后

在实际的前端开发中尤为是移动端开发,不少小伙伴都很喜欢使用相似 translateZ(0)等属性来进行所谓的硬件加速,以提高性能,达到优化页面动态效果的目的,但仍是要注意凡事过犹不及,应用硬件加速的同时也要注意到千万别踩坑。
关于合成层的更细致具体的讲解,能够仔细学习下下面的参考文章(尤为是前三篇哦)。
最后祝愿热爱技术的你我始终坚持在探索技术的路上奋力前行!

参考文章:
无线性能优化:Composite
DOM to Screen
CSS GPU Animation: Doing It Right
web优化之composite
详谈层合成(composite)
CSS3硬件加速也有坑