深入理解浏览器渲染原理

news2024/10/6 8:39:39

文章目录

  • 浏览器是如何渲染页面的
  • 渲染流程
    • 解析HTML(构建DOM树)
      • 解析过程中遇到JS代码
    • 样式计算
      • 1. 解析CSS代码
      • 2. 转换样式表中的属性值,使其标准化
      • 3. 计算DOM树中每个节点的具体样式
        • CSS继承规则
        • CSS层叠规则
    • 布局
    • 分层
      • 分层
      • update layer tree
    • 绘制
    • 栅格化操作
      • 合成线程首先对每个图层进行分块
      • 分块完成后会进入栅格化阶段
      • 栅格化过程通常使用GPU加速
  • 什么是reflow(重排)?
  • 什么是repaint(重绘)?
  • 为什么transform的效率高?
  • 为什么transform的效率高?


浏览器是如何渲染页面的

当浏览器的网络线程收到HTML文档后,会产生一个渲染任务,并将其传递给渲染主线程的消息队列,在事件循环的作用下,渲染主线程取出消息队列中的渲染任务,开始渲染流程。

img

渲染流程

渲染流程分为多个阶段,分别是:构建DOM树、样式计算、布局、分层、绘制、分块、光栅化、(合成)、画、像素信息,每个阶段都有明确的输入和输出,上个阶段的输出会成为下个阶段的输入。输入的 HTML 经过这些子阶段,最后输出像素。我们把这样的一个处理流程叫做渲染流水线

img

image-20231004142512356

解析HTML(构建DOM树)

根据文档定义构建一棵 DOM 树,DOM 树是由 DOM 元素及属性节点组成的。

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

img

解析的过程中遇到CSS解析CSS,遇到JS执行JS。
为了提高解析效率,浏览器在开始解析之前,会启动一个预解析的线程,率先下载HTML中的外部CSS文件和外部JS文件。如果主线程解析到link位置,此时外部的CSS文件还没有下载解析好,主线程不会等待,继续解析后续的HTML。这是因为下载和解析CSS的工作是在预解析线程中进行的。这就是CSS不会阻塞HTML解析的根本原因。

image-20231004151443408

解析过程中遇到JS代码

如果主线程解析到script位置,会停止解析HTML,转而等待JS文件下载好,并将全局代码解析执行完后才能后,才能继续解析HTML。这是因为JS代码的执行过程可能会修改当前的DOM树,所以DOM树的生成必须暂停。这就是JS会阻塞HTML解析的根本原因。

img

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

样式计算

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

  1. 解析CSS
  2. 转换样式表中的属性值,使其标准化
  3. 计算DOM节点的具体样式

1. 解析CSS代码

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

image-20231004152225727

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

image-20231004153542804

CSS文件的主要来源:

img

渲染引擎会把获取到的CSS文本全部转换成CSSStyleSheet结构中的数据,上图三种来源的CSS都可以转换成CSSStyleSheet结构中的数据。

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

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

什么是标准化?就是将如2em、blue、bold这些不容易被渲染引擎理解的的属性值转换成渲染引擎容易理解的、标准化的属性值,这个过程就是属性值标准化。

img

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

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

CSS继承规则

CSS继承就是每个DOM节点都包含有父节点的样式。

将过程2中的样式表最终应用到DOM节点的效果如下:

img

img

CSS层叠规则

CSS层叠规则(Cascading rules)是用于解决相同选择器对同一个元素应用多个样式规则时的冲突情况的一套规则。

浏览器会根据选择器的优先级确定应用哪个样式规则。当多个样式规则具有相同的优先级时,后定义的规则将覆盖先定义的规则。

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

布局

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

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

img

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

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

大部分时候,DOM树和布局树不一定是一一对应的

img

img

img

在执行布局操作的时候,会把布局运算的结果重新写回布局树中,所以布局树既是输入内容也是输出内容,这是布局阶段一个不合理的地方,因为在布局阶段并没有清晰地将输入内容和输出内容区分开来。

分层

如果是首次渲染,那就是分层,如果是更新操作,那就是update layer tree

现在我们有了布局树,而且每个元素的具体位置信息都计算出来了,那么接下来是不是就要开始着手绘制页面了?答案依然是否定的。

分层

因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-index做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。浏览器页面实际上被分成了很多图层,这些图层叠加后合成了最终的页面。

我们可以通过开发者工具中的图层来查看当前页面的分层

img

可以移动或者旋转查看当前页的分层

img

图层和布局树的节点之间有什么关系?

通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层。如下图中的 span 标签没有专属图层,那么它们就从属于它们的父节点图层。但不管怎样,最终每一个节点都会直接或者间接地从属于一个层。

img

什么条件下渲染引擎会为特定节点创建新图层呢?

  1. 条件一:拥有层叠上下文属性的元素会被提升为单独的一层。

页面是个二维平面,但是层叠上下文能够让 HTML 元素具有三维概念,这些 HTML 元素按照自身属性的优先级分布在垂直于这个二维平面的 z 轴上。

img

  1. 条件二:需要裁减的地方会被创建为图层

当文字内容很多,超出规定的显示区域,设置了overflow属性,这时候就产生了剪裁,渲染引擎会把没有被裁掉的的内容显示出来。出现这种裁剪情况的时候,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层。

image-20231004171033657

update layer tree

这一步实际上是更新Render Layer的层叠排序关系

绘制

在完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制,主线程会为每一个层单独产生绘制指令集,用于描述这一层的内容如何画出来。

image-20231004171509334

栅格化操作

绘制列表只是用来记录绘制顺序和绘制指令的列表,当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程

那么接下来合成线程是怎么工作的呢?

img

合成线程首先对每个图层进行分块

什么是视口(viewport)?

img

在有些情况下,有的图层可以很大,比如有的页面你使用滚动条要滚动好久才能滚动到底部,但是通过视口,用户只能看到页面的很小一部分,所以在这种情况下,要绘制出所有图层内容的话,就会产生太大的开销,而且也没有必要。基于这个原因,合成线程就会将图层划分成图块(tile)

img

合成线程会从线程池中拿出多个线程来完成分块工作

img

分块完成后会进入栅格化阶段

**栅格化是将视口附近的图块来优先生成位图(优先处理靠近视口的块),**而图块是栅格化执行的最小单位。栅格化的本质是坐标变换、几何离散化、然后再填充。

img

image-20231004174049409

名词解释:位图

就是数据结构里常说的位图。你想在绘制出一个图片,你应该怎么做,显然首先是把这个图片表示为一种计算机能理解的数据结构:用一个二维数组,数组的每个元素记录这个图片中的每一个像素的具体颜色。所以浏览器可以用位图来记录他想在某个区域绘制的内容,绘制的过程也就是往数组中具体的下标里填写像素而已。

渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的,运行方式如下图所示:

img

栅格化过程通常使用GPU加速

通常,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。相信你还记得,GPU 操作是运行在 GPU 进程中,如果栅格化操作使用了 GPU,那么最终生成位图的操作是在 GPU 中完成的,这就涉及到了跨进程操作。具体形式你可以参考下图:

img

最后一个阶段就是画了,合成层拿到每个层、每个块的位图后,生成一个个的指引(quad)信息,指引会标识出每个位图应该画到屏幕的那个位置,以及会考虑到旋转、缩放等变形,变形发生在合成线程,与主线程无关,这就是transform效率高的本质原因。合成线程会把quad提交给GPU进程,由GPU进程产生系统调用,提交给GPU硬件,完成最终的屏幕成像。

image-20231004142512356

名词解释:event

Input event handlers

Compositor线程接收用户的交互输入(比如touchmove、scroll、click等)。然后commit给Main线程,这里有两点规则需要注意:

  • 并不是所有event都会commit给Main线程,部分操作比如单纯的滚动事件,打字等输入,不需要执行JS,也没有需要重绘的场景,Compositor线程就自己处理了,无需请求Main线程
  • 同样的事件类型,不论一帧内被Compositor线程接收多少次,实际上commit给Main线程的,只会是一次,意味着也只会被执行一次。(HTML5标准里scroll事件是每帧触发一次),所以自带了相对于动画的节流效果!scroll、resize、touchmove、mousemove等事件,由于Compositor Thread的机制原因,都会每一帧只执行一次。

什么是reflow(重排)?

reflow的本质就是重新计算layout树。当进行了会影响布局树的操作后,需要重新计算布局树,会引发布局。为了避免连续的多次操作导致布局树反复计算,浏览器会合并这些操作,当JS代码全部完成后再进行统一计算。所以,改动属性造成的reflow是异步完成的。也同样因为如此,当JS获取布局属性时,就可能造成无法获取到最新的布局信息。浏览器在反复权衡下,最终决定获取属性立即reflow。

img

例如:获取父级元素左边界的偏移值(Element.offsetLeft),但在此之前我们进行了样式或者dom修改,这个操作还在队列中没有执行,那么浏览器为了让我们获取正确的offsetLeft(虽然之前的操作可能不会影响offsetLeft的值),就会立即执行队列里的操作。

Snipaste_2023-10-01_09-26-23.jpg

所以我们知道了,就是这个特殊操作会影响浏览器正常的执行和渲染,假设我们频繁执行这样的特殊操作,就会打断浏览器原来的节奏,增大开销。
而这个特殊操作,具体指的就是:

  • elem.offsetLeft,elem.offsetTop,elem.offsetWidth,elem.offsetHeight,elem.offsetParent
  • elem.clientLeft, elem.clientTop, elem.clientWidth, elem.clientHeight
  • elem.getClientRects(), elem.getBoundingClientRect()
  • elem.scrollWidth, elem.scrollHeight
  • elem.scrollLeft, elem.scrollTop

从上图可以看出,如果你通过JavaScript或者CSS修改元素的集合位置属性,例如改变元素的宽度、高度等,那么浏览器会触发重新布局,解析之后的一系列子阶段,这个过程就叫重排。无疑,重排需要更新完整的渲染流水线,所以开销也是最大的。

什么是repaint(重绘)?

repaint的本质就是重新根据分层信息计算了绘制指令。当改动了可见洋是皇后,就需要重新计算,会引发repaint。由于元素布局信息也属于可见样式,所以reflow一定会引起repaint。

img

从上图可以看出,如果修改了元素的背景颜色,那么布局阶段将不被执行,因为并没有引起集合位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率回避重排操作高一些。

为什么transform的效率高?

因为transform既不要布局也不要绘制,渲染引擎将跳过布局和绘制,只执行渲染流程最后一个draw阶段。

img

由于draw阶段在合成线程中,所以transform的变化几乎不会影响渲染主线程。反之,渲染主线程无论如何忙碌,也不会影响transform的变化。

集合位置的变换,所以就直接进入了绘制阶段,然后执行之后的一系列子阶段,这个过程就叫重绘。相较于重排操作,重绘省去了布局和分层阶段,所以执行效率回避重排操作高一些。

为什么transform的效率高?

因为transform既不要布局也不要绘制,渲染引擎将跳过布局和绘制,只执行渲染流程最后一个draw阶段。

[外链图片转存中…(img-lRndy2wJ-1696423853929)]

由于draw阶段在合成线程中,所以transform的变化几乎不会影响渲染主线程。反之,渲染主线程无论如何忙碌,也不会影响transform的变化。

img

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

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

相关文章

博物馆藏品管理系统-美术馆藏品管理系统

一、项目背景 文物作为前史留存下来最为珍贵的遗物,具有非常高的科学价值和艺术价值,博物馆的存在便是为了保存这些珍贵的前史文化遗产,所以对博物馆的建造必定要重视品质问题。对博物馆的库存办理工作也必定要注意细节,不能出一…

【LeetCode热题100】--20.有效的括号

20.有效的括号 使用栈&#xff1a; class Solution {public boolean isValid(String s) {Stack<Character> stack new Stack<>();int num s.length();for(int i 0;i<num;i){char c s.charAt(i);if(c(||c[||c{){stack.push(c);}else if(stack.isEmpty() ||c…

矩阵求导中的分子布局和分母布局

1.求偏导的自变量的符号区别 使用标量、向量和矩阵总共有九种可能性。请注意&#xff0c;当我们考虑每个自变量和因变量中更多数量的分量时&#xff0c;我们可能会留下非常多的可能性。下表收集了最能以矩阵形式最整齐地组织的六种导数。 在这里&#xff0c;我们使用了最一般…

AI配套的技术: 矢量数据库的概念

一、说明 随着人工智能的快速采用和围绕大型语言模型发生的创新&#xff0c;我们需要在所有这些的中心&#xff0c;能够获取大量数据&#xff0c;将其上下文化&#xff0c;处理它&#xff0c;并使其能够有意义地搜索。 为原生整合生成式 AI 功能而构建的生成式 AI 流程和应用程…

Java+Redis:布隆过滤器,打造高效数据过滤神器!

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是尘缘&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f449;点击这里&#xff0c;就可以查看我的主页啦&#xff01;&#x1f447;&#x…

基于蝴蝶优化的BP神经网络(分类应用) - 附代码

基于蝴蝶优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于蝴蝶优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.蝴蝶优化BP神经网络3.1 BP神经网络参数设置3.2 蝴蝶算法应用 4.测试结果&#xff1a;5.M…

IIC学习笔记(参考小梅哥教程)

IIC: inter-integrated circuit bus ,即 集成电路总线&#xff0c;串行通信&#xff0c;多主从架构&#xff0c;半双工&#xff08;对讲机&#xff09;&#xff0c;小数据量场合&#xff0c;短距离传输。 速率&#xff1a;100kb/s 、 400kb/s 、 3.4Mkb/s 传输单位&#xff1…

《C和指针》笔记31:多维数组的数组名、指向多维数组的指针、作为函数参数的多维数组

文章目录 1. 指向多维数组的数组名2. 指向多维数组的指针3. 作为函数参数的多维数组 1. 指向多维数组的数组名 我们知道一维数组名的值是一个指针常量&#xff0c;它的类型是“指向元素类型的指针”&#xff0c;它指向数组的第1个元素。那么多维数组的数组名代表什么呢&#x…

【C++】你看懂C++的类和对象了么

目录 类 默认成员函数 构造函数 析构函数 拷贝构造函数 赋值运算符重载 运算符重载 赋值运算符重载 前置和后置重载 const成员 取地址及const取地址操作符重载 再谈构造函数 构造函数体赋值 初始化列表 explicit关键字 static成员 友元 友元函数 友元类 内…

Springboot+vue的时间管理系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的时间管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的时间管理系统&#xff0c;采用M&#xff08;model&#xff0…

计算机毕设 大数据工作岗位数据分析与可视化 - python flask

文章目录 0 前言1 课题背景2 实现效果3 项目实现3.1 概括 3.2 Flask实现3.3 HTML页面交互及Jinja2 4 **完整代码**5 最后 0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的要…

使用VSCode插件开发Hyperledger Fabric智能合约(链码)

背景 开发Fabric链码对于开发者而言步骤繁琐&#xff1a;需要部署节点、安装链码、重启网络等操作。当前VSCode中的插件“Hyperledger Fabric Debugger”可以帮助我们迅速开发智能合约。 使用步骤 安装插件 在VSCode中安装Hyperledger Fabric Debugger插件 打开要开发链码的…

【LeetCode热题100】--35.搜索插入位置

35.搜索插入位置 使用二分查找&#xff1a; class Solution {public int searchInsert(int[] nums, int target) {int low 0,high nums.length -1;while(low < high){//注意每次循环完都要计算midint mid (low high)/2;if(nums[mid] target){return mid;}if(nums[mid]…

SE、CBAM、ECA 、CA注意力机制

文章目录 1. SE (Squeeze-and-Excitation)2. CBAM (Convolutional Block Attention Module)3. ECA (Efficient Channel Attention)4. CA (Coordinate Attention) 1. SE (Squeeze-and-Excitation) SENet是通道注意力机制的典型实现 对于SENet而言&#xff0c;其重点是获得输入进…

螺杆支撑座有哪些品牌?

螺杆支撑座是机械设备中重要的支撑部件&#xff0c;用于固定和支撑螺杆&#xff0c;以确保机械设备的稳定性和精度。以下是一些生产螺杆支撑座的品牌以及它们的特点&#xff1a; 1、NSK&#xff1a;提供各种高质量的轴承和机械部件&#xff0c;他们的螺杆支撑座采用先进的制造技…

2023.9.26 IO 文件操作详解

目录 文件 文件路径 文件类型 Java 文件操作 文件系统操作 文件内容操作 字节流 InputStream OutputStream 字符流 Reader Writer 补充 close 的必要性 Scanner 的基本了解 文件 当前指硬盘上的文件和文件夹相对于 变量 在内存中&#xff0c;文件 则是在硬盘上 …

竞赛选题 机器视觉目标检测 - opencv 深度学习

文章目录 0 前言2 目标检测概念3 目标分类、定位、检测示例4 传统目标检测5 两类目标检测算法5.1 相关研究5.1.1 选择性搜索5.1.2 OverFeat 5.2 基于区域提名的方法5.2.1 R-CNN5.2.2 SPP-net5.2.3 Fast R-CNN 5.3 端到端的方法YOLOSSD 6 人体检测结果7 最后 0 前言 &#x1f5…

sheng的学习笔记-【中文】【吴恩达课后测验】Course 2 - 改善深层神经网络 - 第二周测验

课程2_第2周_测验题 目录&#xff1a;目录 第一题 1.当输入从第8个mini-batch的第7个的例子的时候&#xff0c;你会用哪种符号表示第3层的激活&#xff1f; A. 【  】 a [ 3 ] { 8 } ( 7 ) a^{[3]\{8\}(7)} a[3]{8}(7) B. 【  】 a [ 8 ] { 7 } ( 3 ) a^{[8]\{7\}(3)} a…

【iptables 实战】9 docker网络原理分析

在开始本章阅读之前&#xff0c;需要提前了解以下的知识 阅读本节需要一些docker的基础知识&#xff0c;最好是在linux上安装好docker环境。提前掌握iptables的基础知识&#xff0c;前文参考【iptables 实战】 一、docker网络模型 docker网络模型如下图所示 说明&#xff1…

23.2 Bootstrap框架3

1.卡片 1.1卡片样式 在Bootstrap 5中, .card, card-header, .card-body, .card-footer类是用于创建卡片样式.下面是这些类的简单介绍: * 1. .card: 用于创建一个基本的卡片容器它作为一个包裹元素,通常与其他卡片类一起使用.* 2. .card-header: 用于创建卡片的头部部分.通常在…