企业🤖AI Agent构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] > **首先说明,这里讨论的是 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 的工作原理,它对于快速滚动也十分有效。 # 渲染原理 一个 Web 页面的展示,简单来说可以认为经历了以下下几个步骤。 ![](https://img.alicdn.com/tps/TB1eabOLpXXXXX3XFXXXXXXXXXX-1093-167.jpg_720x720.jpg) * JavaScript:一般来说,我们会使用 JavaScript 来实现一些视觉变化的效果。比如做一个动画或者往页面里添加一些 DOM 元素等。 * Style:计算样式,这个过程是根据 CSS 选择器,对每个 DOM 元素匹配对应的 CSS 样式。这一步结束之后,就确定了每个 DOM 元素上该应用什么 CSS 样式规则。 * Layout:布局,上一步确定了每个 DOM 元素的样式规则,这一步就是具体计算每个 DOM 元素最终在屏幕上显示的大小和位置。web 页面中元素的布局是相对的,因此一个元素的布局发生变化,会联动地引发其他元素的布局发生变化。比如,`<body>`元素的宽度的变化会影响其子元素的宽度,其子元素宽度的变化也会继续对其孙子元素产生影响。因此对于浏览器来说,布局过程是经常发生的。 * Paint:绘制,本质上就是填充像素的过程。包括绘制文字、颜色、图像、边框和阴影等,也就是一个 DOM 元素所有的可视效果。一般来说,这个绘制过程是在多个层上完成的。 * Composite:渲染层合并,由上一步可知,对页面中 DOM 元素的绘制是在多个层上进行的。在每个层上完成绘制过程之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。对于有位置重叠的元素的页面,这个过程尤其重要,因为一旦图层的合并顺序出错,将会导致元素显示异常。 ![](https://box.kancloud.cn/3acbfdd9c60d21cb151b93f26bb438e1_650x268.png) 在 Chrome 中其实有几种不同的层类型: * RenderLayers 渲染层,这是负责对应 DOM 子树 * GraphicsLayers 图形层,这是负责对应 RenderLayers子树。 在浏览器渲染流程中提到了composite概念,在 DOM 树中每个节点都会对应一个 LayoutObject,当他们的 LayoutObject 处于相同的坐标空间时,就会形成一个 RenderLayers ,也就是渲染层。RenderLayers 来保证页面元素以正确的顺序合成,这时候就会出现层合成(composite),从而正确处理透明元素和重叠元素的显示。 某些特殊的渲染层会被认为是合成层(Compositing Layers),合成层拥有单独的 GraphicsLayer,而其他不是合成层的渲染层,则和其第一个拥有 GraphicsLayer 父层公用一个。 而每个GraphicsLayer(合成层单独拥有的图层) 都有一个 GraphicsContext,GraphicsContext 会负责输出该层的位图,位图是存储在共享内存中,作为纹理上传到 GPU 中,最后由 GPU 将多个位图进行合成,然后显示到屏幕上。 # 如何变成合成层 * 3D 或透视变换(perspective transform) CSS 属性 * 使用加速视频解码的 元素 拥有 3D * (WebGL) 上下文或加速的 2D 上下文的 元素 * 混合插件(如 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创建不必要的合成层,提升渲染性能,移动端优化效果尤为明显。 # 在chrome查看渲染层 其实在chrome中,也为我们提供了相关插件供我们查看页面渲染层的分布情况以及GPU的占用率: * chrome开发者工具菜单→more tools→Layers(开启渲染层功能模块) * chrome开发者工具菜单→more tools→rendering(开启渲染性能监测工具) 执行上面的操作后,你会在浏览器里看到这样的效果: ![](https://box.kancloud.cn/9a18098f413231505f91301e0812e734_1920x951.png) * 最先是页面右上方的小黑窗:其实提示已经说的很清楚了,它显示的就是我们的GPU占用率,能够让我们清楚地知道页面是否发生了大量的重绘。 * Layers版块:这就是用于显示我们刚提到的DOM渲染层的工具了,左侧的列表里将会列出页面里存在哪些渲染层,还有这些渲染层的详细信息。 * Rendering版块:这个版块和我们的控制台在同一个地方,大家可别找不到它。 前三个勾选项是我们最常使用的 * Paint flashing:勾选之后会对页面中发生重绘的元素高亮显示 * Layer borders:和我们的Layer版块功能类似,它会用高亮边界突出我们页面中的各个渲染层 * FPS meter:就是开启我们在(一)中提到的小黑窗,用于观察我们的GPU占用率 # 参考资料 [浏览器渲染流程&Composite(渲染层合并)简单总结](https://segmentfault.com/a/1190000014520786) [前端性能优化之 Composite](https://segmentfault.com/a/1190000014520786)