浏览器的渲染流程

浏览器渲染整体流程

  • HTML解析
  • 样式计算
  • 布局
  • 分层
  • 生成绘制指令
  • 分块
  • 光栅化
  • 绘制

当我们在浏览器中输入然后发送内容,浏览器的网络线程会发送 http 请求,和服务器之间进行通信,之后将拿到的 html 封装成一个渲染任务,并将其传递给渲染主线程的消息队列。在事件循环机制的作用下,渲染主线程取出消息队列中的渲染任务,开启渲染流程。

去除 DNS 查找等这些细枝末节的工作,整个大的部分可以分为两个,那就是网络渲染

这里主要聊一聊渲染

解析HTML

首先第一步就是浏览器会将接收到的HTML 代码进行解析,构建DOM树。

HTML文档由一系列标记(标签)组成,每个标记都有其特定的含义和属性。浏览器通过解析HTML文档,逐个读取和处理这些标记,并根据它们的关系构建DOM树。

解析HTML的过程可以分为以下几个步骤:

  1. 标记化(Tokenization):浏览器将HTML文档分割成一个个标记,包括开始标签、结束标签、属性和文本内容等。这些标记被称为令牌(Tokens)。

  2. 构建节点对象:对于每个令牌,浏览器会创建相应的节点对象,并将其添加到DOM树中。节点对象可以分为元素节点、文本节点、注释节点等不同类型。

  3. 构建父子关系:浏览器会根据开始标签和结束标签之间的嵌套关系,构建父子关系。即将子节点添加到父节点下。

  4. 处理属性:对于开始标签中的属性,浏览器会解析并将其转换为节点对象的属性。每个属性都包含一个名称和一个值,浏览器会将它们存储在节点对象中,以便后续使用。

  5. 处理文本内容:对于文本内容,浏览器会创建文本节点,并将其添加到相应的父节点下。这些文本节点表示HTML文档中的纯文本内容。

  6. 处理注释和其他特殊标记:除了元素和文本节点外,HTML文档还可以包含注释、DOCTYPE声明、CDATA节和其他特殊标记。浏览器也会解析并处理这些特殊标记。

  7. 错误处理:在解析HTML过程中,如果遇到不符合规范的标记或语法错误,浏览器会尽可能地进行容错处理。它会尝试修复错误,并继续构建DOM树。但是,有些错误可能无法修复,导致DOM树无法完全构建。

将整个字符串进行了标记化之后,就能够在此基础上构建出对应的 DOM 树出来。
为了提高解析效率,浏览器在开始解析前,会启动一个预解析的线程,率先下载 HTML 中的外部 CSS 文件和外部的 JS 文件。

在解析 HTML 的过程中,我们可以能会遇到诸如 style、link 这些标签,这是和我们网页样式相关的内容。此时就会涉及到 CSS 的解析。

如果渲染主线程解析到 link 位置,此时外部的 CSS 文件还没有下载解析好,主线程不会等待,而是继续解析后续的 HTML 。这是因为下载和解析 CSS 的工作是在预解析线程中进行的。这就是 CSS 不会阻塞 HTML 解析的根本原因。

最终,CSS 的解析在经历了从字节数据、字符串、标记化后,最终也会形成一颗 CSSOM 树。

预解析线程除了下载外部 CSS 文件以外,还会下载外部 JS 文件,如果主线程解析到 script 位置,会停止解析HTML ,等待 JS 文件下载好,并将全局代码解析并执行完成后,才能继续解析 HTML

  • 解析JavaScript代码:当浏览器遇到JavaScript代码时,会逐行解析并执行代码。解析过程包括词法分析、语法分析和生成抽象语法树(AST)等步骤。

  • 执行JavaScript代码:一旦解析完成,浏览器会执行JavaScript代码。代码中可能包含对DOM树的操作、事件处理、网络请求等操作。

  • 修改DOM和样式:JavaScript代码可以通过操作DOM树和CSS样式来修改页面的结构和样式。这可能会触发重新构建渲染树、布局计算和绘制页面的过程。

  • 异步加载:浏览器还支持异步加载JavaScript文件,可以通过<script>标签的asyncdefer属性来控制脚本的加载和执行时机。

因为 JS 代码的执行过程可能会修改当前的 DOM 树,所以需要等待js执行完成。这就是 JS 会阻塞 HTML 解析的根本原因。 这也是都建议将 script 标签放在 body 标签底部的原因。

html 复制代码
<html>
  <head>
    ...
  </head>
  <body>
    <div></div>
    <script src="..."></script>
  </body>
</html>

第一步完成后,会得到 DOM 树和 CSSOM 树,浏览器的默认样式、内部样式、外部样式、行内样式均会包含在 CSSOM 树中。

样式计算

拥有了 DOM 树我们还不足以知道页面的外貌,因为我们通常会为页面的元素设置一些样式。主线程会遍历得到的 DOM 树,依次为树中的每个节点计算出它最终的样式,称之为 Computed Style

  1. 选择器匹配:对于每个节点,浏览器会遍历CSSOM树,匹配适用于该节点的所有选择器。这个过程称为选择器匹配(Selector Matching)。浏览器会从右向左进行匹配,并使用各种优化策略来提高匹配效率。

  2. 计算最终样式:一旦确定了适用于节点的所有选择器,浏览器会计算这些选择器对应的最终样式。它会考虑选择器的优先级、继承规则和层叠顺序等因素。

  3. 属性值计算:对于每个最终样式属性,浏览器会计算其具体值。这可能涉及到单位转换、颜色计算、字体渲染等操作。

  4. 继承处理:某些样式属性具有继承性,即子节点会继承父节点的样式。浏览器会根据继承规则,将适用的父节点样式属性应用到子节点上。

  5. 计算结果存储:最后,浏览器会将计算得到的最终样式存储在渲染树(Render Tree)中的每个节点上。渲染树只包含需要显示的节点和其对应的样式信息。

需要注意的是,样式计算是一个相对耗时的操作,特别是在处理大型复杂页面时。为了提高性能,浏览器会使用一些优化策略,例如缓存计算结果、增量更新等。同时,开发者也可以通过优化CSS选择器、减少样式规则和避免频繁修改样式等方式来提升页面渲染性能。

这一步完成后,我们就得到一棵带有样式的 DOM 树。也就是说,经过样式计算后,之前的 DOM 数和 CSSOM 数合并成了一颗带有样式的 DOM 树。

布局

前面这些步骤完成之后,渲染进程就已经知道页面的具体文档结构以及每个节点拥有的样式信息了,可是这些信息还是不能最终确定页面的样子。还需要通过布局(layout )来计算出每个节点的几何信息(geometry)。

生成布局树的具体过程是:

  1. 布局计算:对于每个节点,浏览器会进行布局计算,确定其在屏幕上的位置和大小。这个过程是递归进行的,从渲染树的根节点开始,逐级向下计算。

  2. 盒模型计算:对于每个元素节点,浏览器会计算其盒模型(Box Model)。盒模型包括元素的内容区域、内边距、边框和外边距等部分。浏览器会考虑CSS样式中设置的宽度、高度、内外边距等属性,并结合父元素和兄弟元素的布局信息进行计算。

  3. 文字排版:对于包含文本内容的节点,浏览器会进行文字排版。这涉及到字体渲染、行高计算、文本折行等操作,以确定文本在元素内的布局。

  4. 流式布局:布局计算过程中,浏览器会根据元素的定位属性(如position、float等)和文档流(Flow)规则,确定元素在页面中的位置。这包括块级元素的垂直排列和行内元素的水平排列等操作。

  5. 尺寸调整:在布局计算过程中,如果发现某个节点的尺寸发生变化(例如内容变化、窗口大小调整等),浏览器会重新进行布局计算,以确保页面的正确显示。

  6. 布局结果存储:最后,浏览器会将布局计算得到的位置和大小信息存储在渲染树中的每个节点上。这些信息将用于后续的绘制过程。

需要注意的是,布局是一个相对耗时的操作,特别是在处理大型复杂页面时。为了提高性能,浏览器会使用一些优化策略,例如增量更新、异步布局等。同时,开发者也可以通过优化CSS样式、减少DOM操作和避免频繁修改尺寸等方式来提升页面渲染性能。

分层

  1. 构建图层树:在分层之前,浏览器会根据渲染树(Render Tree)和一些优化策略,将页面的部分内容划分为多个图层。每个图层包含一组相关的渲染对象,例如一个元素及其子元素、一个图片等。图层的划分可以基于一些规则,例如3D变换、透明度、视频等。

  2. 图层绘制:每个图层都会被单独绘制。浏览器会将每个图层的内容绘制到一个独立的位图(Bitmap)上。这个过程可以利用硬件加速技术,例如GPU加速,以提高绘制性能。

  3. 图层合成:在所有图层都被绘制后,浏览器会将这些位图合成为最终的页面显示。合成过程可以利用硬件加速技术,在GPU上进行高效的像素操作和混合。

  4. 分层优化:分层技术可以提高页面渲染性能和流畅度。它可以减少不必要的重绘和布局计算,并允许浏览器对每个图层进行独立处理和优化。例如,在滚动过程中,只有涉及到的图层需要重新绘制,其他图层可以保持不变。

需要注意的是,过多的图层可能会增加内存和绘制开销,并可能导致性能下降。因此,在使用分层技术时,需要权衡图层数量和复杂度,以及设备的硬件能力。开发者可以通过CSS属性(例如transformopacity等)和JavaScript API(例如requestAnimationFrame)来控制和优化页面的分层。 分层的好处在于,将来某一个层改变后,仅会对该层进行后续处理,从而提升效率。

滚动条、堆叠上下文、transformopacity 等样式都会或多或少的影响分层结果,也可以通过使用 will-change 属性来告诉浏览器对其分层。

生成绘制指令

分层工作结束后,接下来就是生成绘制指令。

渲染主线程会为每个层单独产生绘制指令集,用于描述这一层的内容该如何画出来。

  1. 对于可见的节点,浏览器会根据其类型和样式属性生成相应的绘制指令。不同类型的节点可能对应不同的绘制操作,例如矩形、文本、图片等。

  2. 在生成绘制指令时,浏览器会考虑元素之间的层叠关系和CSS属性(例如z-index),以确定它们在页面上的绘制顺序。较高层级的元素会在较低层级的元素之上进行绘制。

  3. 根据节点的类型和样式属性,浏览器会生成相应的绘制指令。这些指令描述了如何绘制节点的内容,例如创建矩形、渲染文本、加载图片等。

  4. 生成的绘制指令可能会根据需要进行排序,以优化渲染性能。例如,将相邻的矩形合并为一个更大的矩形,减少绘制操作次数。

这一步只是生成诸如上面代码的这种绘制指令集,还没有开始执行这些指令。

另外,还有一个重要的点你需要知道,生成绘制指令集后,渲染主线程的工程就暂时告一段落,接下来主线程将每个图层的绘制信息提交给合成线程,剩余工作将由合成线程完成。

分块

合成线程首先对每个图层进行分块,将其划分为更多的小区域。

合成线程首先对每个图层的分块进行处理,这包括对每个分块进行颜色调整、滤镜效果、透明度等操作。然后,合成线程将处理后的分块按照其在原始图像中的位置进行组合,形成最终的合成图像。

在进行分块处理和合成时,合成线程需要考虑不同图层之间的混合模式和透明度。例如,如果一个图层具有不透明度,则需要将其与其他图层进行混合以产生正确的效果。

通过将每个图层的处理任务分配给不同的线程,并行地进行处理,可以提高合成速度和效率。这样可以充分利用多核处理器和并行计算能力,加快整个合成过程。

总之,合成线程首先对每个图层进行分块,并将它们分配给不同的线程进行处理。然后,它将处理后的分块按照其在原始图像中的位置组合起来形成最终的合成图像。这种并行化和分布式处理方式可以提高合成速度和效率。

光栅化

分块完成后,进入光栅化阶段。所谓光栅化,就是将每个块变成位图。

光栅化的操作,并不由合成线程来做,而是会由合成线程将块信息交给 GPU 进程,以极高的速度完成光栅化。

GPU 进程会开启多个线程来完成光栅化,并且优先处理靠近视口区域的块。

光栅化过程涉及以下几个步骤:

  1. 扫描转换:将矢量图形或图像分割成一系列扫描线。扫描线是水平方向上的一条线,沿着图像从上到下进行扫描。

  2. 边界检查:对于每个扫描线,检查与之相交的矢量边界。确定边界与扫描线相交的点。

  3. 插值计算:对于每个相交点,使用插值算法计算出其在矢量边界上的位置。这样可以确定每个相交点在栅格中所占据的像素位置。

  4. 像素填充:将计算得到的像素位置填充为相应颜色值。这样就完成了从矢量数据到栅格数据的转换。

光栅化过程可以通过硬件加速或软件实现。在硬件加速中,专用图形处理单元(GPU)负责执行光栅化操作,以提高性能和效率。而在软件实现中,光栅化算法通常由计算机图形学库或软件程序执行。

绘制

最后一步,我们总算迎来了真正的绘制。

当所有的图块都被栅格化后,合成线程会拿到每个层、每个块的位图,从而生成一个个「指引(quad)」信息。

指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放等变形。

变形发生在合成线程,与渲染主线程无关,这就是 transform 效率高的本质原因。

绘制阶段包括以下几个步骤:

  1. 创建绘图上下文:浏览器会创建一个用于绘制的绘图上下文(Graphics Context),它可以是基于硬件加速的GPU上下文或者软件渲染器。

  2. 绘制背景:首先,浏览器会将背景色或背景图片应用到相应的区域。这包括填充整个视口或特定元素的背景。

  3. 绘制边框:如果元素有边框样式,浏览器会根据指定的边框宽度、颜色和样式来绘制边框。

  4. 绘制内容:接下来,浏览器会根据元素的内容类型进行相应的绘制。例如,对于文本内容,浏览器会将字体、字号、颜色等属性应用到文字,并将其渲染为像素。对于图像、视频等媒体内容,浏览器会将其解码并显示在相应区域。

  5. 绘制阴影和特效:如果元素有阴影、渐变、透明度等特效,浏览器会根据指定的样式来绘制这些特效。

  6. 合成图层:如果页面中使用了多个图层,浏览器会将这些图层按照正确的顺序进行合成,以确保正确的覆盖关系和透明度效果。

  7. 输出到屏幕:最后,浏览器将绘制好的像素数据输出到屏幕上,以显示给用户。

绘制阶段是浏览器渲染流程中的最后一步,它将经过布局和绘制计算得到的图层和元素转换为实际的像素数据,并在屏幕上显示出来。这样用户就可以看到最终渲染好的网页内容。

总结

  1. 解析HTML:浏览器会将接收到的HTML代码进行解析,构建DOM树。

  2. 样式计算:浏览器会解析CSS样式表,构建CSSOM树。然后将DOM树和CSSOM树结合起来,生成渲染树。

  3. 布局:布局计算会确定每个元素在屏幕上的位置和大小。浏览器会遍历渲染树,计算每个元素的布局信息。

  4. 分层:为了提高渲染性能,浏览器会将渲染树分成多个图层。每个图层都有自己的绘制指令和绘制顺序。

  5. 生成绘制指令:浏览器会遍历图层,并生成绘制指令。绘制指令描述了如何绘制每个元素,包括颜色、边框等信息。

  6. 分块:为了提高渲染性能,浏览器会将页面划分成多个块(或称为矩形区域)。每个块都有自己的绘制指令。

  7. 光栅化:光栅化是将矢量图形转换为位图的过程。浏览器会将每个块的绘制指令转换为位图。

  8. 绘制:浏览器会将位图绘制到屏幕上。这个过程包括将位图合成、混合和显示。

整个流程是一个逐步迭代的过程,从解析HTML开始,经过样式计算、布局、分层、生成绘制指令、分块、光栅化和绘制等多个步骤,最终完成页面的显示。

相关推荐
qq_3901617718 分钟前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test1 小时前
js下载excel示例demo
前端·javascript·excel
Yaml41 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事1 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶1 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo1 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
理想不理想v1 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript
知孤云出岫1 小时前
web 渗透学习指南——初学者防入狱篇
前端·网络安全·渗透·web
贩卖纯净水.1 小时前
Chrome调试工具(查看CSS属性)
前端·chrome
栈老师不回家2 小时前
Vue 计算属性和监听器
前端·javascript·vue.js