浏览器工作原理与实践--渲染流程(上):HTML、CSS和JavaScript,是如何变成页面的

news2024/9/23 19:59:12

在上一篇文章中我们介绍了导航相关的流程,那导航被提交后又会怎么样呢?就进入了渲染阶段。这个阶段很重要,了解其相关流程能让你“看透”页面是如何工作的,有了这些知识,你可以解决一系列相关的问题,比如能熟练使用开发者工具,因为能够理解开发者工具里面大部分项目的含义,能优化页面卡顿问题,使用JavaScript优化动画流程,通过优化样式表来防止强制同步布局,等等。

既然它的功能这么强大,那么今天,我们就来好好聊聊渲染流程。

通常,我们编写好HTML、CSS、JavaScript等文件,经过浏览器就会显示出漂亮的页面(如下图所示),但是你知道它们是如何转化成页面的吗?这背后的原理,估计很多人都答不上来。

渲染流程示意图

从图中可以看出,左边输入的是HTML、CSS、JavaScript数据,这些数据经过中间渲染模块的处理,最终输出为屏幕上的像素。

这中间的渲染模块就是我们今天要讨论的主题。为了能更好地理解下文,你可以先结合下图快速抓住HTML、CSS和JavaScript的含义:

HTML、CSS和JavaScript关系图

从上图可以看出,HTML的内容是由标记和文本组成。标记也称为标签,每个标签都有它自己的语义,浏览器会根据标签的语义来正确展示HTML内容。比如上面的<p>标签是告诉浏览器在这里的内容需要创建一个新段落,中间的文本就是段落中需要显示的内容。

如果需要改变HTML的字体颜色、大小等信息,就需要用到CSS。CSS又称为层叠样式表,是由选择器和属性组成,比如图中的p选择器,它会把HTML里面<p>标签的内容选择出来,然后再把选择器的属性值应用到<p>标签内容上。选择器里面有个color属性,它的值是red,这是告诉渲染引擎把<p>标签的内容显示为红色。

至于JavaScript(简称为JS),使用它可以使网页的内容“动”起来,比如上图中,可以通过JavaScript来修改CSS样式值,从而达到修改文本颜色的目的。

搞清楚HTML、CSS和JavaScript的含义后,那么接下来我们就正式开始分析渲染模块了。

由于渲染机制过于复杂,所以渲染模块在执行过程中会被划分为很多子阶段,输入的HTML经过这些子阶段,最后输出像素。我们把这样的一个处理流程叫做渲染流水线,其大致流程如下图所示:

渲染流水线示意图

按照渲染的时间顺序,流水线可分为如下几个子阶段:构建DOM树、样式计算、布局阶段、分层、绘制、分块、光栅化和合成。内容比较多,我会用两篇文章来为你详细讲解这各个子阶段。接下来,在介绍每个阶段的过程中,你应该重点关注以下三点内容:

  • 开始每个子阶段都有其输入的内容;

  • 然后每个子阶段有其处理过程;

  • 最终每个子阶段会生成输出内容。

理解了这三部分内容,能让你更加清晰地理解每个子阶段。

构建DOM树

为什么要构建DOM树呢?这是因为浏览器无法直接理解和使用HTML,所以需要将HTML转换为浏览器能够理解的结构——DOM树。

这里我们还需要简单介绍下什么是树结构,为了更直观地理解,你可以参考下面我画的几个树结构:

树结构示意图

从图中可以看出,树这种结构非常像我们现实生活中的“树”,其中每个点我们称为节点,相连的节点称为父子节点。树结构在浏览器中的应用还是比较多的,比如下面我们要介绍的渲染流程,就在频繁地使用树结构。

接下来咱们还是言归正传,来看看DOM树的构建过程,你可以参考下图:

DOM树构建过程示意图

从图中可以看出,构建DOM树的输入内容是一个非常简单的HTML文件,然后经由HTML解析器解析,最终输出树状结构的DOM。

为了更加直观地理解DOM树,你可以打开Chrome的“开发者工具”,选择“Console”标签来打开控制台,然后在控制台里面输入“document”后回车,这样你就能看到一个完整的DOM树结构,如下图所示:

DOM可视化

图中的document就是DOM结构,你可以看到,DOM和HTML内容几乎是一样的,但是和HTML不同的是,DOM是保存在内存中树状结构,可以通过JavaScript来查询或修改其内容。

那下面就来看看如何通过JavaScript来修改DOM的内容,在控制台中输入:

document.getElementsByTagName("p")[0].innerText = "black"

这行代码的作用是把第一个<p>标签的内容修改为black,具体执行结果你可以参考下图:

通过JavaScript修改DOM

从图中可以看出,在执行了一段修改第一个<p>标签的JavaScript代码后,DOM的第一个p节点的内容成功被修改,同时页面中的内容也被修改了。

好了,现在我们已经生成DOM树了,但是DOM节点的样式我们依然不知道,要让DOM节点拥有正确的样式,这就需要样式计算了。

样式计算(Recalculate Style)

样式计算的目的是为了计算出DOM节点中每个元素的具体样式,这个阶段大体可分为三步来完成。

1. 把CSS转换为浏览器能够理解的结构

那CSS样式的来源主要有哪些呢?你可以先参考下图:

HTML加载CSS的三种方式

从图中可以看出,CSS样式来源主要有三种:

  • 通过link引用的外部CSS文件

  • <style>标记内的 CSS

  • 元素的style属性内嵌的CSS

和HTML文件一样,浏览器也是无法直接理解这些纯文本的CSS样式,所以当渲染引擎接收到CSS文本时,会执行一个转换操作,将CSS文本转换为浏览器可以理解的结构——styleSheets。

为了加深理解,你可以在Chrome控制台中查看其结构,只需要在控制台中输入document.styleSheets,然后就看到如下图所示的结构:

styleSheets

从图中可以看出,这个样式表包含了很多种样式,已经把那三种来源的样式都包含进去了。当然样式表的具体结构不是我们今天讨论的重点,你只需要知道渲染引擎会把获取到的CSS文本全部转换为styleSheets结构中的数据,并且该结构同时具备了查询和修改功能,这会为后面的样式操作提供基础。

2. 转换样式表中的属性值,使其标准化

现在我们已经把现有的CSS文本转化为浏览器可以理解的结构了,那么接下来就要对其进行属性值的标准化操作。

要理解什么是属性值标准化,你可以看下面这样一段CSS文本:

body { font-size: 2em }
p {color:blue;}
span  {display: none}
div {font-weight: bold}
div  p {color:green;}
div {color:red; }

可以看到上面的CSS文本中有很多属性值,如2em、blue、bold,这些类型数值不容易被渲染引擎理解,所以需要将所有值转换为渲染引擎容易理解的、标准化的计算值,这个过程就是属性值标准化。

那标准化后的属性值是什么样子的?

标准化属性值

从图中可以看到,2em被解析成了32px,red被解析成了rgb(255,0,0),bold被解析成了700……

3. 计算出DOM树中每个节点的具体样式

现在样式的属性已被标准化了,接下来就需要计算DOM树中每个节点的样式属性了,如何计算呢?

这就涉及到CSS的继承规则和层叠规则了。

首先是CSS继承。CSS继承就是每个DOM节点都包含有父节点的样式。这么说可能有点抽象,我们可以结合具体例子,看下面这样一张样式表是如何应用到DOM节点上的。

body { font-size: 20px }
p {color:blue;}
span  {display: none}
div {font-weight: bold;color:red}
div  p {color:green;}

这张样式表最终应用到DOM节点的效果如下图所示:

计算后DOM的样式

从图中可以看出,所有子节点都继承了父节点样式。比如body节点的font-size属性是20,那body节点下面的所有节点的font-size都等于20。

为了加深你对CSS继承的理解,你可以打开Chrome的“开发者工具”,选择第一个“element”标签,再选择“style”子标签,你会看到如下界面:

样式的继承过程界面

这个界面展示的信息很丰富,大致可描述为如下。

  • 首先,可以选择要查看的元素的样式(位于图中的区域2中),在图中的第1个区域中点击对应的元素,就可以在下面的区域查看该元素的样式了。比如这里我们选择的元素是<p>标签,位于html.body.div.这个路径下面。

  • 其次,可以从样式来源(位于图中的区域3中)中查看样式的具体来源信息,看看是来源于样式文件,还是来源于UserAgent样式表。这里需要特别提下UserAgent样式,它是浏览器提供的一组默认样式,如果你不提供任何样式,默认使用的就是UserAgent样式。

  • 最后,可以通过区域2和区域3来查看样式继承的具体过程。

以上就是CSS继承的一些特性,样式计算过程中,会根据DOM节点的继承关系来合理计算节点样式。

样式计算过程中的第二个规则是样式层叠。层叠是CSS的一个基本特征,它是一个定义了如何合并来自多个源的属性值的算法。它在CSS处于核心地位,CSS的全称“层叠样式表”正是强调了这一点。关于层叠的具体规则这里就不做过多介绍了,网上资料也非常多,你可以自行搜索学习。

总之,样式计算阶段的目的是为了计算出DOM节点中每个元素的具体样式,在计算过程中需要遵守CSS的继承和层叠两个规则。这个阶段最终输出的内容是每个DOM节点的样式,并被保存在ComputedStyle的结构内。

如果你想了解每个DOM元素最终的计算样式,可以打开Chrome的“开发者工具”,选择第一个“element”标签,然后再选择“Computed”子标签,如下图所示:

DOM元素最终计算的样式

上图红色方框中显示了html.body.div.p标签的ComputedStyle的值。你想要查看哪个元素,点击左边对应的标签就可以了。

布局阶段

现在,我们有DOM树和DOM树中元素的样式,但这还不足以显示页面,因为我们还不知道DOM元素的几何位置信息。那么接下来就需要计算出DOM树中可见元素的几何位置,我们把这个计算过程叫做布局。

Chrome在布局阶段需要完成两个任务:创建布局树和布局计算。

1. 创建布局树

你可能注意到了DOM树还含有很多不可见的元素,比如head标签,还有使用了display:none属性的元素。所以在显示之前,我们还要额外地构建一棵只包含可见元素布局树。

我们结合下图来看看布局树的构造过程:

布局树构造过程示意图

从上图可以看出,DOM树中所有不可见的节点都没有包含到布局树中。

为了构建布局树,浏览器大体上完成了下面这些工作:

  • 遍历DOM树中的所有可见节点,并把这些节点加到布局树中;

  • 而不可见的节点会被布局树忽略掉,如head标签下面的全部内容,再比如body.p.span这个元素,因为它的属性包含 dispaly:none,所以这个元素也没有被包进布局树。

2. 布局计算

现在我们有了一棵完整的布局树。那么接下来,就要计算布局树节点的坐标位置了。布局的计算过程非常复杂,我们这里先跳过不讲,等到后面章节中我再做详细的介绍。

在执行布局操作的时候,会把布局运算的结果重新写回布局树中,所以布局树既是输入内容也是输出内容,这是布局阶段一个不合理的地方,因为在布局阶段并没有清晰地将输入内容和输出内容区分开来。针对这个问题,Chrome团队正在重构布局代码,下一代布局系统叫LayoutNG,试图更清晰地分离输入和输出,从而让新设计的布局算法更加简单。

总结

好了,今天正文就到这里,我画了下面这张比较完整的渲染流水线,你可以结合这张图来回顾下今天的内容。

渲染流水线图

从图中可以看出,本节内容我们介绍了渲染流程的前三个阶段:DOM生成、样式计算和布局。要点可大致总结为如下:

  • 浏览器不能直接理解HTML数据,所以第一步需要将其转换为浏览器能够理解的DOM树结构;

  • 生成DOM树后,还需要根据CSS样式表,来计算出DOM树所有节点的样式;

  • 最后计算DOM元素的布局信息,使其都保存在布局树中。

到这里我们的每个节点都拥有了自己的样式和布局信息,那么后面几个阶段就要利用这些信息去展示页面了,由于篇幅限制,剩下的这些阶段我会在下一篇文章中介绍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1542632.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

服务端高并发分布式结构

前言 本文以⼀个 “电子商务” 应用为例&#xff0c;介绍从⼀百个到千万级并发情况下服务端的架构的演进过程&#xff0c;同时列举出每个演进阶段会遇到的相关技术&#xff0c;让大家对架构的演进有⼀个整体的认知&#xff0c;方便⼤家对后续知识做深⼊学习时有⼀定的整体视野…

二次开发Flink-coGroup算子支持迟到数据通过测输出流提取

目录 1.背景 2.coGroup算子源码分析 2.1完整的coGroup算子调用流程 2.2coGroup方法入口 2.3 CoGroupedStreams对象分析 2.4WithWindow内部类分析 2.5CoGroupWindowFunction函数分析 3.修改源码支持获取迟到数据测输出流 3.1复制CoGroupedStreams 3.2新增WithWindow.si…

YiYi-Web项目介绍

YiYi-Web项目介绍 1. 简介2. 使用2.1 后端开发环境2.2 前端开发环境 3. 测试环境&#xff1a;4. 更新日志5. 打包情况6.项目截图 本项目前端是html、css、js、jQuery基础技术。 后端都是最新的SpringBoot技术&#xff0c;不分离版本&#xff0c; 是最基础的项目开发教程&#x…

Spark Map 和 FlatMap 的比较

Spark Map 和 FlatMap 的比较 本节将介绍Spark中map(func)和flatMap(func)两个函数的区别和基本使用。 函数原型 map(func) 将原数据的每个元素传给函数func进行格式化&#xff0c;返回一个新的分布式数据集。 flatMap(func) 跟map(func)类似&#xff0c;但是每个输入项和…

Flink GateWay、HiveServer2 和 hive on spark

Flink SQL Gateway简介 从官网的资料可以知道Flink SQL Gateway是一个服务&#xff0c;这个服务支持多个客户端并发的从远程提交任务。Flink SQL Gateway使任务的提交、元数据的查询、在线数据分析变得更简单。 Flink SQL Gateway的架构如下图&#xff0c;它由插件化的Endpoi…

孩子看书用白光还是暖白光好呢?五大护眼台灯推荐,必选!

孩子看书时&#xff0c;选择合适的光线是至关重要的。白光与暖白光各有优劣&#xff0c;白光亮度高、色彩还原性好&#xff0c;适合需要高度集中注意力和精细操作的场合&#xff1b;而暖白光则更加柔和、不刺眼&#xff0c;有助于营造轻松的阅读氛围。为了帮助家长更好地为孩子…

coco creator 3.x: 居中展示

coco creator 3.x 里面有很多需要居中处理&#xff0c;下面积累记录一些日常的实验。 下面一张图如下&#xff0c;在这张图里面创建了一个3x3的展示。但是这个展示过程并不在居中展示。而它的容器节点 恰恰就在中心点位置。所以在创建后&#xff0c;布局预制体元素并不能实现居…

Docker容器初始

华子目录 docker简介虚拟化技术硬件级虚拟化硬件级虚拟化历史操作系统虚拟化历史基于服务的云计算模式 什么是dockerDocker和传统虚拟化方式的不同之处为什么要使用docker&#xff1f;Docker 在如下几个方面具有较大的优势 对比传统虚拟机总结docker应用场景docker改变了什么 基…

iOS开发 - 转源码 - __weak问题解决

iOS开发 - 转源码 - __weak问题解决 在使用clang转换OC为C代码时&#xff0c;可能会遇到以下问题 cannot create __weak reference in file using manual reference 原因 __weak弱引用是需要runtime支持的&#xff0c;如果我们还只是使用静态编译&#xff0c;是无法正常转换的…

MultiArch与Ubuntu/Debian 的交叉编译

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;基于ARM 的Linux系统的交叉编译 下一篇&#xff1a;MultiArch与Ubuntu/Debian 的交叉编译 警告&#xff1a; 本教程可能包含过时的信息。 什么是“MultiArch” OpenCV 可能…

家乡特色推荐系统设计与实现|SpringBoot+ Mysql+Java+ B/S结构(可运行源码+数据库+设计文档)

本项目包含可运行源码数据库LW&#xff0c;文末可获取本项目的所有资料。 推荐阅读100套最新项目 最新ssmjava项目文档视频演示可运行源码分享 最新jspjava项目文档视频演示可运行源码分享 最新Spring Boot项目文档视频演示可运行源码分享 2024年56套包含java&#xff0c;…

flink join的分类

带窗口的join 下图是固定窗口,同样的还有滑动窗口和会话窗口join DataStream<Integer> orangeStream = ...; DataStream<Integer> greenStream = .

物联网应用技术中的stm32该怎么学,该从哪入手?

物联网应用技术中的stm32该怎么学&#xff0c;该从哪入手&#xff1f; STM32是只物联网中的一部分&#xff0c;单纯的学个STM32是没法满足物联网开发需求的&#xff0c;实际产品开发过程中会考虑成本等多种因素选择合适的方案&#xff0c;比如使用单片机还是stm32或是更高端的芯…

上位机图像处理和嵌入式模块部署(qmacvisual点线测量)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 上面一篇文章&#xff0c;我们完成了直线的拟合操作。在实际场景中&#xff0c;拟合之后更多地是需要进行长度的测量。既然是测量&#xff0c;那么…

Linux:传输层和UDP的传输原理

文章目录 端口号端口号理解端口号的划分 netstatUDP协议 之前结束了对于应用层的理解&#xff0c;那么从本篇开始往后&#xff0c;将会深入到传输层当中进行理解&#xff0c;尝试打通整个网络的协议栈 从对于之前的理解来说&#xff0c;在应用层涉及到的知识体系是相当庞大的&…

力扣450 删除二叉搜索树中的节点 Java版本

文章目录 题目描述思路代码 题目描述 给定一个二叉搜索树的根节点 root 和一个值 key&#xff0c;删除二叉搜索树中的 key 对应的节点&#xff0c;并保证二叉搜索树的性质不变。返回二叉搜索树&#xff08;有可能被更新&#xff09;的根节点的引用。 一般来说&#xff0c;删除…

力扣100热题[哈希]:最长连续序列

原题&#xff1a;128. 最长连续序列 题解&#xff1a; 官方题解&#xff1a;. - 力扣&#xff08;LeetCode&#xff09;题解&#xff0c;最长连续序列 &#xff1a;哈希表 官方解题思路是先去重&#xff0c;然后判断模板长度的数值是否存在&#xff0c;存在就刷新&#xff0c…

算法打卡day15

今日任务&#xff1a; 1&#xff09;110.平衡二叉树 2&#xff09;257. 二叉树的所有路径 3&#xff09;404.左叶子之和 110.平衡二叉树 题目链接&#xff1a;110. 平衡二叉树 - 力扣&#xff08;LeetCode&#xff09; 给定一个二叉树&#xff0c;判断它是否是高度平衡的二叉树…

爬楼梯C语言

方法一&#xff1a;动态规划 int climbStairs(int n) {int f[100] {0};f[0] 0;f[1] 1;f[2] 2;for(int i 3;i<n;i)f[i] f[i-1] f[i-2];//可能是从i-1阶爬上第i阶&#xff0c;也有可能是从i-2阶 return f[n]; } 方法二&#xff1a;滚动数组 int climbStairs(int n){int…

DCDC60V80V100V转12V5V1A2A降压恒压芯片 惠海半导体原厂

H4020是一种内置40V耐压MOS&#xff0c;并且能够实现精确恒压以及恒流的同步降压型DC-DC转换器&#xff1b;支持1A持续输出电流输出电压可调&#xff0c;最大可支持 100%占空比&#xff1b;通过调节 FB 端口的分压电阻&#xff0c;可以输出 2.5V到 24V 的稳定电压 。H4020 具有…