本文主要是记录下浏览器(chrome)的渲染过程,想到什么写什么。
简单划分的浏览器渲染步骤
- 从网页输入URL到构建DOM树
- DOM树解析成绘图上下文
- 根据绘图上下文生成图像
下面根据这三个步骤来分别简单探索一下。
网页输入URL到构建DOM树
一个老生常谈的问题:从浏览器输入URL到页面渲染完毕,中间发生了哪些事情?(之后单独写一篇总结一下,会补上链接)
这个问题感觉避不开,就简单的写一下,当顺便复习了。
- DNS域名解析
- 建立TCP连接
- 发送HTTP请求
- 服务器处理请求
- 返回响应结果
- 关闭TCP连接
- 浏览器解析HTML
- 浏览器布局渲染
基本是以上八个阶段,可以看到,浏览器拿到返回数据准备开始解析、渲染界面的步骤从第7步才开始,之前6个步骤都是在进行前后端通信,嗯,这里又想起来三次握手,这个问题暂且不讨论。本文讨论的范围是从第7部开始的解析过程。
这里写的浏览器解析HTML实际上对应的就是前面渲染步骤的第一步,构建DOM树。
浏览器布局渲染对应的就是前面的后两步。
废话不多说了,进入正题。
首先,浏览器解析HTML或者说构建DOM树这一过程,是由浏览器中的谁处理的呢?
这个过程由浏览器的内核模块(渲染引擎)来处理的。
上面只是一个简单的示意图,把浏览器内核渲染相关的功能列举了一下,值得一提的是最新的Chrome浏览器的内核由以前的webkit换成了Blink内核,不过对我们理解整个浏览器的渲染国营的影响不大,只是几个流程中理解的对象名称发生了一些变化,后续会提到。
浏览器拿到后端返回的资源后,开始对资源进行解析,为什么这里又说是资源,不说是HTML了,因为后端返回的可能不仅仅是HTML,可能还有CSS、JS文件等等,所以浏览器不光是对HTML进行解析,还有CSS、JS等文件都会进行处理,本文为了省事,就简单按照上面图片包含的内容分析了,有兴趣的可以去深入了解一下。
又跑偏了,还是扔图看吧,简单直观。
以上就是我所理解的整个流程,在当前我们讨论的生成DOM树这一步,经过了JavaScript引擎,因为JS中可能会有一些影响DOM结构的DOM操作,还有可能对DOM的样式产生改变。事实上,这一过程比较简单,若深究DOM树的生成原理,请自行研究内核的相关原理,这里就不展开了(我也不会 =。=)
DOM树解析为绘图上下文
这个过程是我们要研究的重点内容。
上一个步骤中通过HTML解释器生成的DOM树,会和CSS解释器对css样式产生的css rules tree进行“对应粘贴”,生成一个等待处理的Render Tree。其中css rules tree 是CSS解释器对css样式解析成的树形数据结构,它会和DOM树中的对应的节点进行“粘贴”操作,或者说绑定操作,使得样式和DOM节点能够一一对应起来。
接下来是重头戏了。我想丢个图,表达我对这个过程的重视。
上面图中把它放在渲染的步骤中,有些不合适,但是这样有助于理解整个过程,我就不作过多的纠正,与其说是过程,不如说是一种映射关系。
从Nodes(Render Tree)到Layout Objects
DOM树(RenderTree)中每个Node节点上都会有一个Layout Object,这个Layout Object知道对应的Node节点应该在屏幕上渲染在哪个位置(布局),或者说它知道它对应的节点应该渲染在哪一个渲染层上(Paint Layer)
从 Layout Objects 到 Paint Layers
一般来说,对于拥有相同的空间坐标的Layout Objects来说,它们会共同属于一个Paint Layer(渲染层)。
关于渲染层,又想到一个概念:层叠上下文(stacking contest),这也是个值得探究的话题。Paint Layers起初是用来实现层叠上下文的,这样可以保证页面元素正确的合成(composite)顺序和展示效果。
根据创建Paint Layer的原因,可以将它分为3类:
- Normal Paint Layer
- Overflow Clip PaintLayer
- No Paint Layer
满足以上条件的 Layout Object 会拥有独立的渲染层,而其他的 Layout Object 则和其第一个拥有渲染层的父元素共用一个。
上述流程中最后一步提到了composite的概念。这是因为某些特殊的渲染层会被认为是合成层(Compositing Layers),合成层拥有单独的 GraphicsLayer,而其他不是合成层的渲染层,则和其第一个拥有 GraphicsLayer 父层公用一个。
而每个GraphicsLayer(合成层单独拥有的图层) 都有一个 GraphicsContext,GraphicsContext 会负责输出该层的位图,位图是存储在共享内存中,作为纹理上传到 GPU 中,最后由 GPU 将多个位图进行合成,然后显示到屏幕上。这个就是我们前面第三步将绘图上下文生成图像的这一过程。
如何变成合成层
满足以下任意情况便会创建层:
- 3D 或透视变换(perspective transform) CSS 属性
- 使用加速视频解码的 元素 拥有 3D
- (WebGL) 上下文或加速的 2D 上下文的 元素
- 混合插件(如 Flash)
- 对自己的 opacity 做 CSS动画或使用一个动画变换的元素
- 拥有加速 CSS 过滤器的元素
- 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在自己的层里)
- 元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
合成层的优点
一旦Paint Layer提升为了合成层就会有自己的绘图上下文,并且会开启硬件加速,有利于性能提升。
- 合成层的位图,会交由 GPU 合成,比 CPU 处理要快
- 当需要 repaint 时,只需要 repaint 本身,不会影响到其他的层
- 对于 transform 和 opacity 效果,不会触发 layout 和 paint
注意:
- 提升到合成层后合成层的位图会交GPU处理,但请注意,仅仅只是合成的处理(把绘图上下文的位图输出进行组合)需要用到GPU,生成合成层的位图处理(绘图上下文的工作)是需要CPU。
- 当需要repaint的时候可以只repaint本身,不影响其他层,但是paint之前还有style, layout,那就意味着即使合成层只是repaint了自己,但style和layout本身就很占用时间。
- 仅仅是transform和opacity不会引发layout 和paint,那么其他的属性不确定。
总结合成层的优势:一般一个元素开启硬件加速后会变成合成层,可以独立于普通文档流中,改动后可以避免整个页面重绘,提升性能。
性能优化点:
- 提升动画效果的元素 合成层的好处是不会影响到其他元素的绘制,因此,为了减少动画元素对其他元素的影响,从而减少paint,我们需要把动画效果中的元素提升为合成层。 提升合成层的最好方式是使用 CSS 的 will-change属性。从上一节合成层产生原因中,可以知道 will-change 设置为opacity、transform、top、left、bottom、right 可以将元素提升为合成层。
- 使用 transform 或者 opacity 来实现动画效果, 这样只需要做合成层的合并就好了。
- 减少绘制区域 对于不需要重新绘制的区域应尽量避免绘制,以减少绘制区域,比如一个 fix 在页面顶部的固定不变的导航header,在页面内容某个区域 repaint 时,整个屏幕包括 fix 的 header 也会被重绘。而对于固定不变的区域,我们期望其并不会被重绘,因此可以通过之前的方法,将其提升为独立的合成层。减少绘制区域,需要仔细分析页面,区分绘制区域,减少重绘区域甚至避免重绘。
利用合成层可能踩到的坑
- 合成层占用内存的问题
- 层爆炸,由于某些原因可能导致产生大量不在预期内的合成层,虽然有浏览器的层压缩机制,但是也有很多无法进行压缩的情况,这就可能出现层爆炸的现象(简单理解就是,很多不需要提升为合成层的元素因为某些不当操作成为了合成层)。解决层爆炸的问题,最佳方案是打破 overlap 的条件,也就是说让其他元素不要和合成层元素重叠。简单直接的方式:使用3D硬件加速提升动画性能时,最好给元素增加一个z-index属性,人为干扰合成的排序,可以有效减少chrome创建不必要的合成层,提升渲染性能。
总结:以上就是本文的全部内容,后面关于性能优化的部分都是参考前辈的经验文章作笔记,带上一点自己的思考,学一点东西。
参考:
https://blog.csdn.net/weixin_40581980/article/details/81453283
https://blog.csdn.net/weixin_34268310/article/details/88859536
https://www.jianshu.com/p/99e450fc04a5