渲染顺序
理解这个过程对开拓设计职员来说很关键,它会帮助我们设计站点的时候充分考虑到用户体验和性能问题。页面加载后,浏览器会布局DOM,CSSOM和渲染树,当这些都创建好之后,就会开始在屏幕上绘制每一个元素。
布局操作

首先浏览器给每个渲染树节点创建布局(layout)信息, 它包含了节点的将来显示的位置数据(像素点位置)。这个过程叫布局(Layout),也可以叫回流(reflow), 由于它也可能发生在窗口的大小改变,滚动等事宜或DOM元素操作中。
注:我们该当只管即便避免页面产生多次布局操作,由于这是代价昂贵的操作。
显示操作
到现在为止,我们已经有了一棵渲染树,也便是节点位置列表,包含了须要显示的所有信息。
由于渲染树上的节点可以重叠显示,它们的CSS属性,决定了它们的外不雅观,位置,如何变革(动画)。我了更好的掌握渲染,浏览器引入了层的观点。建立层,浏览器可以高效地在页面生命周期内实行显示操作。层也可以帮助元素以堆栈的形式显示(Z轴方向)。
在每一层,浏览器将会显示元素的每一个像素,比如边框,背景,颜色,阴影,笔墨等。这个过程也叫栅格化。为了改进性能,浏览器必须利用不同的线程去做这种栅格化操作。
这个层跟Photoshop中的层类似,你可以通过Chrome开拓者工具看到页面中的不同层。打开开拓者工具->更多工具->选择层,你将会看到更多细节。
注:栅格化常日是CPU完成的,但现在我们有新技能可以利用GPU来做这个操作,从而提高性能
合成操作
直到现在,我们还没有在显示器上画出任何一个像素。我们有的只是一些不同的层(位图Bitmap Image),这些层将会以特定的顺序被显示出来。在合成操作中,它们将被发送给GPU,终极显示在显示屏上。
一次性发送所有层显然是低效的。由于如果有回流或者重绘操作,这个每次都会发生。以是一个层会被打碎成很多个可显示的小块(Tiles)。你可以在开拓者工具的渲染面板中看到这些小块。
综合上面的信息,我们可以看到浏览器的事宜顺序,我们把这个实行顺序叫做关键渲染路径,如下图。
关键渲染路径(Critical Rendering Path)
浏览器引擎
创建DOM树,CSSOM树和处理渲染逻辑的事情,是被一个叫做浏览器引擎的软件完成的。它内置于浏览器中。这个引擎包含了渲染所须要的所有东西,能够把从HTML字符串文档终极转化成屏幕上的像素点。
如果你听人们谈论WebKit, 他们便是在说浏览器引擎。WebKit是APPLE Safari浏览器的默认引擎。Google Chrome利用的是Blink, 微软最新的Edge浏览器也利用了跟Chrome一样的引擎。还有一些其他公司的,比如Firefox...
浏览器中的渲染过程
我们都知道,JavaScript措辞是遵照ECMAScript标准的, 因此每个JavaScript引擎比如V8,Chakra,Spider Monkey等,都被必须遵守这个标准。
有了这个标准,运行JavaScript就能给我们同等的体验,无论是在浏览器中,还是Node.js中,Deno等环境中运行相同的JS代码,都能给我们相同的结果。。这很棒,并且会提高我们的产品质量。
然而,这种情形在浏览器渲染中就不存在了,只管HTML,CSS,JavaScript,这些措辞的标准被一些机构掌握,但浏览器把它们组合到一起,如何渲染出来,这个过程没有标准化。各个公司就各个公司的办法。比如Chrome就跟Safari的做法不一样。
因此很难预测在一个分外浏览器中的渲染顺序和机制。只管如此,HTML规范也做了些努力来标准化渲染操作。但是浏览器怎么遵守,遵守多少完备取决于他们自己。
除了这些不一致性,有些共通的东西是在所有浏览器中是一样的。 让我们来理解一下常日浏览器渲染事宜的过程是怎么样的。
解析和外部资源
解析是读取HTML布局DOM树的过程。以是这个过程也叫DOM解析,做这个事情的程序叫DOM Parser。
大多数浏览器供应了DOMParser API来布局一个DOM树。你可以试着布局一个DOMParser的实例,然后利用parseFromString方法,看看可以布局出一个什么DOM树。
当浏览器要求一个页面,做事器返回了HTML文本(Content-Type设成text/html), 浏览器可以在只吸收到全体文档中的开始几行或几个字符就开始解析操作。以是浏览器可以增量地布局DOM树,一次一个节点地从头到尾解析。
在上面这个例子中,我们访问incremental.html文件,设置网速只有10kbps,这样它会花很永劫光来下载这个包含了1000个H1元素文件。从下图可以看到,浏览器从最初的收到的一些字节就开始布局DOM树,并把他们显示出来。剩下的东西还在后台下载中,就这样边下载边解析。
上面是该要求的性能图表, 你会看到这些事宜发生的韶光。当他们发生的越早,用时越短,解释用户体验越好。
FP表示首次渲染,表示浏览器开始在显示器上显示东西了。可能便是大略到显示Body中背景的第一个像素。
FCP表示首次内容渲染,解释浏览器已经渲染了图片或笔墨的第一个像素
LCP表示最大内容渲染,解释浏览器渲染了最大的一块笔墨或图片
L表示onload事宜,是由浏览器的window工具发出的。类似的DCL由document工具发出,它冒泡至window,这样你就可以在window工具上监听它。这些事宜有些繁芜,我们接下来谈论它。
只要浏览器解析时碰到外部文件,它就会开始后台下载那个文件(非主线程)。比如JavaScript <script src="url"></script>, CSS <link rel="stylesheet" href="url" />, image <img src="url" />或者其他任何外部资源都会这样。
最主要的便是要记住,解析常日发生在主线程。如果主线程解析JavaScript很忙,DOM解析操作就会停滞事情,直到主线程再次空闲。之以是主要,由于只有script标签(JavaScript文件)会壅塞解析,而其他要求如image,stylesheet, pdf, video等外部文件不会壅塞解析。
解析壅塞脚本
当浏览器碰到script元素,如果是一段内嵌脚本,浏览器停滞HTML解析,立即实行该脚本,然后连续解析HTML。所有内嵌JavaScript都会壅塞HTML解析。
如果script是外部脚本文件,浏览器会停滞主线程事情(停滞DOM解析),去下载js文件并等待其完成下载。当js下载后,浏览器会先实行下载的文件,然后连续主线程的DOM解析事情。如果浏览器创造其余一个script标签,它也会做同样的操作。为什么浏览器要停滞当前的DOM解析事情呢?
我们知道,浏览器从JavaScript运行时暴露了DOM API,意味着我们可以用JavaScript访问或操作DOM元素。这便是那些动态的web框架可以事情的事理。比如React, Vue, Angular...但如果浏览器同时运行DOM解析和实行JS,那就会产生竞争关系,由于俩线程都可能改变DOM,终极导致DOM树不准确,以是DOM解析和JS实行都必须在主线程上。
只管如此,当下载JS文件的时候,停滞DOM解析在大多数情形下是完备没有必要的。以是HTML5增加了async属性给script标签。当浏览器碰到带async的script标签,它下载JS文件的时候不会停滞DOM解析,一旦下载结束,就会壅塞DOM解析并且立即实行JavaScript代码。
我们还有一个更好用的defer属性,它跟async类似,下载的时候不会壅塞DOM解析。不一样的是,当defer文件下载完毕后,不会立即实行,会等到DOM树完备布局完成后才会实行。
在上面的例子中, parser-blocking.html文件,在30元素后,有一个壅塞解析的script, 这便是为什么浏览器开始显示了30个元素,然后停滞了DOM解析,开始下载JavaScript文件。第二个script文件有defer属性,以是它会在DOM树完备建立后才实行。
如果看性能面板,浏览器一开始布局DOM树,有了一些HTML内容时,FP和FCP就发生了。我们就看到一下东西呈现出来了。
LCP发生于5秒后,由于须要处理JavaScript,以是DOM解析就会被壅塞5秒(JS文件的下载韶光),并且只有30个文本元素被显示。但是这些东西还不敷以成为最大的渲染内容(根据Google标准)。一旦JS文件下载并实行完成, DOM解析规复,这时最大内容会被显示出来,以是LCP就触发了。
渲染壅塞-CSS
我们已经知道,任何其他外部资源,除了JavaScript文件,都不会壅塞DOM解析。以是CSS也不会直接壅塞DOM解析。。。等等。。。不会直接壅塞!
!
!
什么意思?实在CSS会壅塞DOM解析,但我们须要先理解渲染过程。
浏览器引擎会将HTML文本变成DOM树,并且它也从stylesheet可以布局CSSOM树。但是DOM树和CSSOM树的布局都是在主线程上进行的,然后它俩合并成渲染树。我们已经知道DOM树是天生是增量的,一边读HTML一边天生节点添加到树上。但这不是CSSOM的布局过程,CSSOM树布局不是增量型的,是必须在某一特定时候发生的。
当浏览器创造<style>块,它会解析所有的内嵌CSS并且更新CSSOM树,然后连续进行正常的DOM解析,inline样式也一样的处理。
然而,如果碰到外部CSS文件,事情就大不一样了。不像外部JavaScript文件,外部CSS文件不是解析壅塞资源,以是浏览器可以在后台连续下载,DOM解析仍旧会连续。
其余,不像HTML,CSSOM布局不是增量型的,它不能边读边被布局。缘故原由是一个文件末端的CSS规则,可能会修正文件最顶部的规则。以是,如果进行增量布局,那就会导致渲染树的多次渲染,因此CSSOM节点随时可能会发生变革。那将会极大的降落用户体验,用户就可能看到页面效果一直的变革。以是当所有CSS规则被处理后,CSSOM树会被更新,然后渲染树也会被更新,末了才在显示器上呈现。
CSS确实是渲染壅塞资源。一旦浏览器发送一个要求去外部stylesheet,渲染树的布局就停滞了。因此关键渲染路径(Critical Redering Path)也会被卡住,什么都显示不了。只管如此,DOM树的解析仍旧连续。
想一下浏览器可能已经利用老的CSSOM树就天生了渲染树,并且显示了一些东西在显示器上,然后又碰到了外部CSS文件,怎么办?这种情形很糟糕,一旦外部CSS文件下载完成,CSSOM树要被更新,渲染树也要被更新,所有那些已经被显示的元素要被新渲染树的内容所替代重新显示,这就会导致闪烁,是很糟糕的用户体验。
以是浏览器会等等stylesheet下载解析完成,这样CSSOM准备好,渲染树就可以准备好,渲染关键路径就不会被壅塞了。基于上述缘故原由,常日建议所有外部CSS文件要尽早的被加载,最好是放在<head>里。
另一种情形,外部JS文件已经被完备下载了,而外部CSS文件仍旧在后台下载,这是浏览器会实行JS文件么?有什么危害么?
我们知道,CSSOM供应了JavaScript API来跟DOM元素的样式进行交互。比如,你可以读取更新元素的背景颜色el.style.backgourndColor 属性。这个与el关联的style属性暴露了CSSOM API。当stylesheet在后台下载的时候,JavaScript仍旧会被实行,由于我们的主线程没有被壅塞。如果我们的JavaScript程序访问DOM元素的CSS属性,它会拿到当前CSSOM上的值。但是一旦stylesheet下载完成并被解析后,这就会导致CSSOM被更新,我们刚才JavaScript拿到的值就过期了,因此,不才载CSS的时候去实行JS是不屈安的,该当只管即便避免。
根据HTML5规范,浏览器可以下载一个JavaScript文件,但不会立即实行,直到所有stylesheets先被处理了。当一个stylesheet壅塞了JavaScript实行,这叫做脚本壅塞CSS。
上面这个例子,script-blocking.html包含了一个link标签(这个是CSS),然后随着一个script标签(这个是JavaScript文件),这里JavaScript文件下载特殊快,没有任何延迟,但CSS文件花费了6秒去下载。以是只管JavaScript文件加载完成,它仍旧不会被浏览器立即实行。只有当CSS文件被处理完,我们才能看到JavaScript输出的Hello World。
文档的DOMContentLoaded事宜
这个事宜DOMContentLoaded(简称DCL)在浏览器已经完成了所有DOM树的构建而发出的。这里有很多成分会影响这个事宜的发生。
如果我们的HTML没有任何脚本,DOM解析不会被壅塞,DCL就会在解析完全个HTML立即发生。如果我们有壅塞的脚本,DCL就会等所有壅塞脚本被下载实行后再发生。
其余,如果我们把CSS考虑进来,事情就变得繁芜了。只管没有外部脚本,DCL仍旧要等所有stylesheets被加载。由于DCL就记录一个韶光点,解释全体DOM树准备好了,但如果CSSOM没有准备好,DOM树并不能被安全的访问。以是大多数浏览器会等CSSOM也准备好才发出这个事宜。
DCL是最主要的网站性能指标之一,我们要优化它,越小越好。最佳实践之一便是利用defer,async标签给script元素。让这些脚本可以在后台下载。第二,我们要优化渲染壅塞的stylesheets.
Window的load事宜
我们已经知道JavaScript可以壅塞DOM树的天生,带其他外部文件弗成,比如CSS,图片,视频等等。
DOMContentLoaded事宜记录了DOM树完成布局并且可以安全访问的韶光点。window.onload记录了当外部的css和其他文件被下载完成的韶光点。
在上面的例子中, rendering.html 文件有一个外部CSS文件在head中,它要花5秒被下载。由于它在head中,以是FP和FCP是在5秒后发生的。这个css文件会壅塞渲染,看不到任何东西。
在那之后,我们有个图片元素,大概须要10秒去下载,以是浏览器仍旧连续在后台下载这个文件,并且连续进行DOM解析和渲染。
接下来,我们有3个JavaScript文件,他们将花3秒,6秒,9秒去下载。更主要的是,他们不是async的,这就意味着统共须要18秒顺序实行下载。而且直到前一个被下载并实行完成后,后面一个才会连续下载实行。只管如此,我们的当代浏览器彷佛采取了新的策略,更高效地下载了它们,以是统共用时9秒旁边。由于末了一个文件下载会影响DCL, 以是DCL发生在9.1秒。
这时仍旧有其他外部资源不才载,便是那张图片,它仍旧在后台下载,大概花了10秒下载完成了,以是window.load事宜在10.2秒被触发,意味着全体页面加载完成。
以上便是浏览器的全体渲染过程,希望可以帮助大家理解它的事理,也希望大家可以批评示正缺点,一起谈论学习。