在浏览器中输入www.baidu.com 浏览器如何渲染出百度页面?
这篇文章不谈网络相关,我们只关注浏览器拿到html文档资源是如何解析的,如何绘制渲染页面。
在浏览器百度页面打开查看源码,可以看到上图所示,它其实是一个html文档,核心是一个字符串。
而浏览器所做的事情就是把这字符串,解析成我们所看到的页面,这篇文章会从浏览器渲染的原理介绍页面渲染的每一个细节步骤
渲染时间点
如上图所示,
- 输入url,浏览器网络线程与服务器通信,拿到html文档。
- 网络线程生产渲染任务,推入消息队列
- 渲染主线程从消息队列中调度渲染任务开始执行(渲染时间点)
渲染流水线
渲染过程主要分为八个步骤、分别为:HTML解析、样式计算、布局、分成、光栅化、绘制、分块、画,每个步骤都会有响应的输入输出。
graph TD
HTML字符串 --> 解析html --> 样式计算 --> 布局 --> 分层 -->绘制 --> 分块 --> 光栅化 --> 画 --> 像素信息
HTML解析-ParseHTML
html字符串解析
把html字符串通过ParseHTML转换成dom对象。
css解析(预解析线程)
根节代表网页中所有样式表:
- 内部样式表 (
<style>) - 外联样式表(
<link ...>) - 行内样式表(
<div style="">) - 浏览器默认样式表
浏览器默认样式表在chrome源码中如图所示
路径 chromium -> third_party ->blink -> renderer -> core -> html -> resources -> html.css
通过查看chrome默认样式源码:
div、p等元素为何独占一行,因为源码中明确设置了display:block
注意:js可以操作dom树也可操作上述样式表对象(StyleSheetList),但是没办法操作浏览器默认样式,可以在浏览器中输入 document.styleSheets 查看
document.styleSheets[0].addRule("div","border:2px solid #CCC") 在百度首页console中输入此行代码,可看到js操作styleSheets
HTML解析过程中遇到css代码怎么办?
通过上述分析得知,html和css的宏观解析逻辑。但是在主线程中是html代码一行一行解析执行的,如果在html解析的过程中遇到了css代码,浏览器应该如何处理呢?
为了提高解析效率,浏览器会启动一个预解析线程,率先下载和解析css
- 渲染主线程开始解析之前开启css预解析线程
- 渲染主线程遇到link标签时,不会阻塞(css预解析线程处理)继续向前解析
- 网络线程下载好css文件后交给css预解析线程处理
- css预解析线程处理好之后推给渲染主线程
- 渲染主线程生产cssom
- 合并当前的dom和cssom
HTML解析过程中遇到JS代码怎么办?
渲染主线程遇到JS代码时必须暂停一切行为(因为js可能会改变dom树和cssom树),等在下载执行完后才能继续
预解析线程可以分担一点下载js的任务
以上最终会得到两颗树 dom树 和cssom树
样式计算 - Reaclculate Style
将dom树和cssom树合并计算(计算后的样式)
样式计算-css计算过程
- 样式基础
- 层叠
样式计算-视觉格式化模型
- 盒模型
- 包含块
计算后的样式如图
布局-Layout
主要算尺寸,位置,高度,宽度等。
dom树和layout树并不是一一对应的。比如display:none,那么layout树中就不会有这个节点。还有一些标签,可看浏览器默认样式表
位置
包含块知识 补充:布局树不是一个js对象,局部树是一个c++对象
分层-layer
由于页面不是静态的,渲染出来的页面是会变化的。分层就是用来更好处理这边变化页面的板块。可在浏览器中查看页面分层
和堆叠上下文有关的属性(z-index)会影响分层。
分层处理由浏览器自行处理,每个浏览器的处理办法不一样。
以百度首页为例
绘制 -Paint
为每一个层生成单独的绘制指令
以一个绘制div为例
- 将比移动某个板块
- 画多宽多高
- 填充颜色
- ... 渲染主线程的工作就到此处结束了。剩余工作就交给其他线程完成。
小结主线程所做的工作
分块-Tiling
分块会将每一层分为很多小的区域
在mac电脑中的活动检测中可以查看到合成线程
光栅化-Raser
光栅化就是将每一个块变成位图,
优先处理靠近视口的快。
位图其实就是每个像素点的信息(颜色)。
此过程会用到GPU加速。
画-Draw
合成线程计算出每个位图在屏幕上的位置,交给GPU最终呈现。
注意css样式的旋转缩放在此阶段才确定。
为什么最后画的使用需要交给GPU去呈现,而不是合成线程直接呈现呢?
- 合成线程在渲染进程中
- 浏览器是以沙河的设计模式,可以和其他系统隔离
- 合成线程是没法去直接调用其他应用程序的,只能通过GPU系统调用。
- 这样设计更安全,浏览器遭受攻击时不会影响操作系统和其他程序。
面试
浏览器是如何渲染页面的?
参考答案
当浏览器的网络线程收到html文档后,会产生一个渲染任务,并将其传递给渲染主线程的消息队列。
在事件循环机制的作用下,渲染主线程取出消息队列中的渲染任务,开启渲染流程。
整个渲染流程分为多个阶段,分别是:HTML解析、样式计算、布局、分成、绘制、分块、画
每个阶段都有明确的输入输出,上一个阶段的输出会成为下一个阶段的输入。
这样,整个的渲染流程就形成了一套组织严密的生产流水线。
渲染的第一步是解析html。
解析过程中遇到css解析css,遇到js执行js。为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程,率先下载html中的外部css文件和外部js文件。
如果主线程解析到link位置,此时外部的css文件还没有下载好,主线程不会等待,继续解析后续的html。这是因为下载和解析css的工作是在预解析线程中进行的,这就是css不会阻塞html解析的根本原因。
如果主线程解析到script位置,会停止解析html,转而等待js文件下载好,并将全局代码解析执行完成后才会继续解析html。这是因为js代码的执行过程可能会修改当前的dom树,所以dom树的生成必须暂停。这就是js阻塞html解析的根本原因。
第一步完成后,会得到dom树和cssom树,浏览器的默认样式、外部样式、内部样式、行内样式均会包含在cssom树中。
渲染的下一步是样式计算。
主线程会遍历得到的dom树,依次为树中的每个节点计算出它最终的样式,称为Compute Style。 这一过程中,很多预设值会变成绝对值,比如red会变成rgb(255,0,0);相对单位会变成绝对单位,比如em会变成px
这一步完成后,会得到一颗带有样式的dom树。
接下来就是布局,布局完成后会的到布局树。
布局阶段会依次遍历dom树的每一个节点,计算每一个节点的几何信息。如节点的宽高、相对包含块的位置。
大部分时候,dom树和布局树并非一一对应。
比如 display:none的节点没有几何信息,因此不会生成到布局树;又比如使用了伪类元素选择器,虽然dom树中不存在这些伪类元素节点,但它们拥有几何信息,所以会生产到布局树中。还有匿名行盒、匿名快盒等等都会导致dom树和布局树无法一一对应。
下一步是分层
主线程会使用一套复杂的策略对整个布局树进行分层。
分层的好处在于,将来某一个层改变后,仅会对该层进行后续处理,从而提高效率。 滚动条、堆叠上下文、transform、opacity等样式都会或多或少的影响分层结果,也可以通过will-change属性更大程度的影响分层结果
再下一步是绘制
主线程会为每一个层单独产生绘制指令集,用于描述这一层的内用该如何画出来。
分块-Tiling
完成绘制后,主线程将每个图层的绘制信息提交给合成线程,剩余工作将由合成线程完成。
和弦线程首先对每个图层进行分块,将其划分为更多的小区域。
它会从线程池中拿去多个线程来完成分块工作。
光栅化
合成线程会将快信息交给GPU进程,以极高的速度完成光栅化。
GPU进程会开启多个线程来进行光栅化,并且优先处理靠近视口区域的块。
光栅化的结果就是一块一块的位图
画
终于到了最后一个阶段了,合成线程拿到每一个层、每一个块的位图后生成指引信息。
指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转,缩放等变形。
变形发生在合成线程,与渲染线程无关,这就是transform效率高的本质原因。
合成线程会把指引星系提交高GPU进程,由GPU进程产生系统调用,提交GPU硬件,完成最终的屏幕成像。