-
Notifications
You must be signed in to change notification settings - Fork 0
Description
我们可能只知道写一些脚本,然后一张好看的页面就出现在了屏幕上。但是浏览器究竟是怎样将我们的HTML,CSS和JavaScript脚本渲染成屏幕上的像素的呢?
从浏览器接收到HTML,CSS和JavaScript脚本,到将它们渲染为像素点的过程中有许多中间步骤。要优化性能,就要了解这些步骤,也就是所谓的关键渲染路径(Critical Rendering Path)。
优化和没优化的页面渲染过程对比图
渲染过程
浏览器渲染大致需要如下的步骤:
- 处理HTML脚本,生成DOM树
- 处理CSS脚本,生成CSSOM树
- 将DOM树和CSSOM树合并为渲染树
- 对渲染树中的内容进行布局,计算每个节点的几何外观
- 将渲染树中的每个节点绘制到屏幕中
这个过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的html都解析完成之后再去构建和布局render树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。
DOM树和CSSOM树合并成一棵渲染树
- 从DOM树的根节点开始,遍历所有的可视节点
有些不可见元素(比如脚本标签,元数据标签之类)会被忽略,因为它们不影响渲染的结果
有些通过CSS隐藏掉的元素也会被忽略,比如上图中的span元素。由于该元素上显式地设置了属性“display:none”,所以不会出现在渲染树上
CSS中“visibility:hidden”和“display:none”是不同的。前者将元素隐藏起来,但是隐藏元素仍然会在页面最
终的布局中占据相应的空间(其实就是一块空白)。然而后者会直接将元素从渲染树中删除,不仅不可见,也不属于最终布局的一部分。
- 对于每个可视节点,从CSSOM中寻找对应的样式规则,并付诸节点
- 输出可视的节点,以及每个节点计算出来的样式
最终的渲染树既包含了所有可视的内容,又包含了相应的样式信息。快要大功告成了!有了这棵渲染树,我们就能进入下一步,布局。
在此之前,我们已经计算了什么节点是可视的以及它们对应的样式是什么,但是我们还没有计算它们在当前设备中准确的位置和尺寸。这正是布局阶段要做的的工作,该阶段在英语中也被称为“回流”(reflow).
- 布局layout 为了算出每个对象的准确大小和位置,浏览器从渲染树的根节点开始遍历,计算页面中每个对象的几何样式。
布局阶段的输出结果称为 “盒模型”(box model)。盒模型精确表达了窗口中每个元素的位置和大小,而且所有的相对的度量单位都被转化成了屏幕上的绝对像素位置。
- 最后阶段,已经知道了哪些节点是可视的、它们的样式和它们的几何外观,我们终于能够将这些信息渲染为屏幕上每个真实的像素点了。这个阶段称为“绘制”,或者“格栅化”(rasterizing)。
css对渲染的影响
在前部分中我们看到了关键的渲染途径需要同时具备DOM(Document Object Mode,文件对象模型)和CSSOM(CSS Object Model,CSS对象模型)来构造渲染树,这证明了一个重要的性能上的可能的结果:HTML和CSS都是阻止渲染的资源。
默认情况下,CSS会被当做渲染中的阻塞性资源,也就是说浏览器即使已经处理好了某些页面内容,只要CSSOM没有构建好,便不会进行渲染。浏览器会在同时具备了DOM和CSSOM时才渲染界面。
js对渲染的影响
JavaScript让我们能够修改页面的每个方面:内容、样式和用户交互行为。然而,JavaScript也能阻止DOM的构建,并延迟页面的渲染。确保你的JavaScript是异步的,并且从关键渲染路径中消除任何不必要的JavaScript,以提供最佳性能。
- JavaScript可以查询和修改DOM和CSSDOM
- CSSDOM阻止JavaScript执行
- JavaScript阻止DOM的构建,除非明确地声明为异步的
脚本是在它被插入到文本中的位置处执行的。当HTML解析器遇到一个脚本标签时,它会暂停对构建DOM的处理,并让出控制权给JavaScript引擎。一旦JavaScript引擎完成了运行,浏览器再从它离开的地方重新开始,并继续进行DOM构建。
执行内联脚本会阻止DOM的构建,这也会延迟初始化渲染。
假如浏览器在我们运行脚本时还没有完成CSSOM的下载和创建会怎么样?答案很简单,并且对于性能不会很好:浏览器会延迟脚本的执行,直到它完成了CSSOM的下载和构建,当我们在等待时,DOM的构建也被阻止了!
脚本在文本中的位置是很关键的
当遇到script标签时DOM的构建会被暂停,直到脚本完成了执行。
JavaScript可以查询和修改DOM和CSSDOM
直到CSSOM准备好了,JavaScript才会执行
在使用外部的JavaScript文件的情况下,浏览器将不得不暂停来等待从磁盘、缓存或远程服务器上获取这个脚本,这会给关键路径的渲染增加数十到数千毫秒的延迟。
event timestamp
- domLoading:这是整个加载进程开始的时间戳。浏览器从这个时间点开始解析收到的HTML页面的第一个字节。
- domInteractive:标记了浏览器完成解析HTML,DOM树构建完毕的时间。
- domContentLoaded:标记了DOM准备就绪且没有样式资源阻碍JavaScript执行的时间点,我们可以开始构建渲染树了。
很多JavaScript框架会在这个事件发生后才开始执行它们自己的逻辑。因此浏览器会通过捕获domContentLoadedEventStart和domContentLoadedEventEnd来计算执行框架的代码逻辑需要多长时间。 - domComplete:不言自明,所有的处理过程结束,所有的页面资源下载完成。浏览器窗口上表示页面还在加载的图标停止旋转。
- loadEvent:作为所有页面加载的最后一步,浏览器会在此时触发onLoad时间,以便开始附加的应用逻辑。
domInteractive:表示DOM准备就绪。
domContentLoaded:表示DOM和CSSOM都准备就绪。如果没有JavaScript阻塞渲染,该事件会在domInteractive事件之后立即触发。
domComplete:表示页面及其附属资源都已经准备就绪。
JS(不包括动态插入的JS)执行完之后,才会触发DOMContentLoaded事件
The DOMContentLoaded event is fired when the document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading
Note: Stylesheet loads block script execution, so if you have a <script> after a <link rel="stylesheet" ...>, the page will not finish parsing – and DOMContentLoaded will not fire – until the stylesheet is loaded.
这么看来,至少可以得出这么一个理论:**DOMContentLoaded事件本身不会等待CSS文件、图片、iframe加载完成**。
它的触发时机是:加载完页面,解析完所有标签(不包括执行CSS和JS),并如规范中所说的设置 interactive 和执行每个静态的script标签中的JS,然后触发。
而JS的执行,需要等待位于它前面的CSS加载(如果是外联的话)、执行完成,因为JS可能会依赖位于它前面的CSS计算出来的样式。





