有了第一节和第二节的铺垫,现在就可以仔细看看浏览器的渲染进程是如何工作的了
渲染进程概览
- 任何在tab中发生的事情都由渲染process负责,也就是将html,javascript,css等文件“翻译”成我们看到的网页,其中的main thread去做绝大多数的任务,此外其他thread也可能被用到,比如web或者是service worker,Compositor和raster thread用于帮助页面更好的渲染
解析
构建DOM
- 根据第一节,渲染进程开始工作的时机是从提交导航开始,同时文档数据(html data)从浏览器process的网络thread流入渲染进程,这个时候main thread就开始工作了:main thread 把html 转换成DOM分为两个大的方面,解析html文档和构建DOM(这两个工作不是独立的,而是同时进行的,)
- 解析html和构建DOM 是有标准可依的;html的解析从不报错这时因为解析html 语法是十分宽容的,如果想了解更多,请移步An introduction to error handling and strange cases in the parser
子资源加载
- 但是网页的资源不仅仅只有这个html文件,还有其他很多的资源比如javascript,css文件,图片等等,所有的资源都需要从网络或者是从本地加载。
- 假设在构建DOM的同时去下载那些子资源,其实这种方式已经比先完全构建好DOM再回头去找需要的资源,然后再下载快多了,但是Chrome的优化机制做得更好,Chrome有“preload scanner”,这个扫描机制类似编译机制一样,只要发现例如img,link等tag的标签就去下载对应的资源(❗️
我的理解,不等DOM构建,也即是不需要等到DOM的构建达到这个资源对应的tag时下载就开始了,比如script标签,如果链接了外部的javascript文件,那么DOM的解析需要等到script的下载,解析,执行完毕再继续,有了这个scanner,就可以预先开始下载,那么block DOM解析的可能就不会有javascript文件的下载了,只有解析和执行这个只是自己的理解TBD1
❗️)
javascript 可以暂停解析
- 就是前一段讲的,如果解析过程中出现了
script
标签,HTML的文档解析将被挂起,下载,解析,执行javascript 文件将优先;原因:因为javascript是可以完全改变文档结构的,所以要先让script执行完毕再继续。
告诉浏览器你要如何加载资源
- 针对javascript文件,有很多方式可以避免上述的阻碍解析,比如给script加上defer或者是async的标签,反之加上preload是告诉浏览器尽早的下载
样式计算
- main thread也要解析和确定DOM节点的样式,也就是解析CSS文件,即使没有CSS文件,Chrome也会给DOM节点应用默认的样式信息
布局
- 知道了DOM结构,知道了每个DOM节点的样式,这还不够,因为仅仅通过这些信息无法把页面精准的渲染出来——缺少了位置信息;布局的过程添加位置信息的过程,从一个DOM树生成一个布局树,这个过程中,
display:none
的元素会被过滤掉不出现在布局树上,但是被hidden
的元素还在,带有内容的伪元素上的内容也会出现在布局树上 - 构建布局树是个很大的任务,想象一下,即使是最简单的顺序段落的罗列也需要考虑字体的大小,换行等,因为这些都会影响到当前元素的尺寸,进而影响其他的元素;更复杂一些,浮动布局,隐藏一些内部,甚至是改变排列方向等等都是非常复杂的任务
绘制
- 布局树已经具有完全的二维的排列信息,但是别忘了,元素还存在垂直于屏幕的纵向排列
- main thread会遍历布局树,然后生成一个绘画记录,这个记录记录着绘制的先后顺序,从而让元素之间的垂直几何关系明确。如果你用javascript操作过canvas,你应该对绘制记录有更深的理解。
更新渲染流水线的成本很大
- 上边讲述的构建DOM,解析样式,构建布局树,生成绘制记录等流程都是向前依赖的,也就是只能按照顺序依次执行,所以更新一次渲染流水线的时间和计算成本都很大。
动画掉帧现象
- 先说原理:人对于每秒高于60帧(FPS)的动画感觉是流畅的,所以屏幕大多数情况下也是60FPS的刷新率,首先说明一个概念,帧(frame)可以理解成是一个图片,那么60FPS就意味着1秒需要有60帧以上的图像连续才能让人感觉是连续的(
这里只是为了大概解释一下,更专业的知识不去讨论
),那么要保证这样的刷新率,最差的情况也要让60张图片均匀的分布在时间轴上,间隔16.6ms,注意这是能达到人眼分辨率的临界的排列情况,所以一旦中间有一帧稍微延后了比如2ms,或者是这帧干脆丢失了,那么我们就会有卡顿的感觉 - 再说问题:因为大量的工作都是main thread在做,比如javascript代码的执行,DOM,布局树和绘制记录的生成都需要在main thread上完成,如果需要有 动画效果,那么浏览器就需要在两帧之间的时间通过渲染流水线(渲染流水线的长短取决于如何改变元素,如果改变了布局,那么就要从头都走一边,如果只是修改了颜色,布局不需要更新)来更新动画,卡顿与否取决于渲染流水线的长短和计算内容的多少,当然,因为javascript的执行也在main thread,所以这也是个可能会造成动画卡顿的原因,
- 可行的解决办法:可以通过将javascript代码拆分成更小的块,然后利用
requestAnimationFrame
每一帧之间的空闲来执行代码从而避免卡顿的现象;当然也可以直接在web worker上执行javascript代码来避免其block 主线程
合成
- 一切准备就绪就要开始绘制网页了,这里插一个概念,就是栅格化,即将上述绘制信息转为屏幕像素的过程,其实也就是显示出来的过程;以前的做法是将viewport内的部分进行栅格化,用户滚动到哪里栅格化就进行到哪里
- 但是先进的浏览器的做法更加复杂,首先会把要绘制的内容分层,然后分别栅格化,最后在另外一个线程——合成线程中进行合成。这样做的结果就是,当用户滚动时,因为分层和栅格化都已经完成了,所以所要做的就是合成,动画同理,如果有动画,所要做的只是将栅格化的层进行移动然后合成为帧展示给用户。
- 可以利用浏览器的layers来查看一个页面的分层情况,个人的经验,建议使用safari进行查看,更加流畅
利用css进行分层
- 上述的分层操作其实是主线程遍历布局树(可以看上边的内容,这时的布局树不仅有二维的位置信息,有关相对垂直位置的绘画记录也都包括了,绘画记录为分层提供了重要信息),然后生成一个layer 树,如果我们发现某些内容应该被放在单独的一层(比如一个可以滑动的菜单),但是分层处理并没有做到的话,可以通过给css添加
will-change
属性来达到人为干预分层 - 也许你会着迷于人为的分更多的层,但是过深的层次会导致合成的效率大大降低,所以谨慎此行为
不在主线程的栅格化和合成
- 直到现在其实都还没有明确的讲解各个步骤具体都是在哪个线程中完成的,这是因为其实直到栅格化和合成之前,所有都是发生在主线程
- 在layer 树构建好之后,主线程会把这个树结构(当然也会有绘画记录)提交给合成线程,合成线程会对每层进行栅格化,但是会有这样的问题,一个layer的高度可能很高,可能是一整个晚网页那么高,那么合成线程不会将这个layer一次性栅格化,而是会把这个layer切成很多的tile,再把这些tile给到栅格线程们(多个栅格线程),栅格线程会把这些tile都栅格化后存放在GPU内存中
- 视窗内的会优先被栅格化;栅格化完成,合成线程就会将tile聚合成draw quads用以生成合成帧(就是最后展示的这一帧,也就是用户看到的东西)
- 之后,这个合成帧会被提交到浏览器process,如果这时另外一个合成帧也从UI thread提交以改变浏览器的UI或者其他的渲染process,那么这这些合成帧都会被发到GPU从而显示在屏幕上,当用户滚动页面,同样的事情就会继续发生
- 在这一段开始的时候就谈到,栅格化和合成的操作不是在主线程上完成的,所以不会被主线程上的操作block,那么如果动画可以在只在合成阶段,其流畅程度将大有保障,具体可以看这里High Performance Animations,换句话说,修改DOM结构和样式将导致主线程的参与从而部分或者整个导致渲染流线的参与,可能导致动画效率大大降低
本节refs:
- 英文原文?
上一节 / 下一节
- Inside look at modern web browser (2) -- 经典问题:从输入URL到网页展示的全部过程
- Inside look at modern web browser(4) -- 浏览器如何处理交互事件
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!