深入理解浏览器渲染原理

文章目录

  • 浏览器是如何渲染页面的
  • 渲染流程
    • 解析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 树中可见元素的几何位置,例如节点的宽高、相对包含块的位置,我们把这个计算过程叫做布局。**布局树的每个节点都记录了x,y坐标和边框尺寸。

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)给合成线程

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

合成线程将每个图层栅格化,然后只需要把可视区的内容组合成一帧,展示给用户即可。由于可能有的图层可能和整个页面一样大,所以合成器线程将图层先进行分块,然后将每个块发送给栅格化进程,栅格进程栅格每个图块,并将它们存储在GPU内存中。

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渲染展示到屏幕上,最终完成屏幕成像。

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.mzph.cn/news/94994.shtml

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

相关文章

WebGL 响应上下文丢失解决方案

目录 响应上下文丢失 如何响应上下文丢失 上下文事件 示例程序(RotatingTriangle_contextLost.js) 响应上下文丢失 WebGL使用了计算机的图形硬件,而这部分资源是被操作系统管理,由包括浏览器在内的多个应用程序共享。在某些特…

逐步解决Could not find artifact com:ojdbc8:jar:12

Could not find artifact com:ojdbc8:jar:12 in central (https://repo.maven.apache.org/maven2) 原因: ojdbc8:jar:12 属于Oracle 数据库链接的一个程序集,缺失的话很有可能会影响数据库链接,蝴蝶效应产生不可预测的BUG!但是版…

OpenGLES:绘制一个混色旋转的3D立方体

效果展示 混色旋转的3D立方体 一.概述 之前关于OpenGLES实战开发的博文,不论是实现相机滤镜还是绘制图形,都是在2D纬度 这篇博文开始,将会使用OpenGLES进入3D世界 本篇博文会实现一个颜色渐变、旋转的3D立方体 动态3D图形的绘制&#xf…

mybatise-plus的id过长问题

一、问题情景 笔者在做mp插入数据库(id已设置为自增)操作时,发现新增数据的id过长,结果导致前端JS拿到的数据出现了精度丢失问题,原因是后端id的类型是Long。在网上查了一下,只要在该属性上加上如下注解就可以 TableId(value &q…

进程调度的时机,切换与过程以及方式

1.进程调度的时机 进程调度(低级调度〉,就是按照某种算法从就绪队列中选择一个进程为其分配处理机。 1.需要进行进程调度与切换的情况 1.当前运行的进程主动放弃处理机 进程正常终止运行过程中发生异常而终止进程主动请求阻塞(如等待l/O)…

大模型部署手记(1)ChatGLM2+Windows GPU

1.简介: 组织机构:智谱/清华 代码仓:https://github.com/THUDM/ChatGLM2-6B 模型:THUDM/chatglm2-6b 下载:https://huggingface.co/THUDM/chatglm2-6b 镜像下载:https://aliendao.cn/models/THUDM/chat…

很普通的四非生,保研破局经验贴

推免之路 个人情况简介夏令营深圳大学情况机试面试结果 预推免湖南师范大学面试结果 安徽大学面试结果 北京科技大学笔试面试结果 合肥工业大学南京航空航天大学面试结果 暨南大学东北大学 最终结果一些建议写在后面 个人情况简介 教育水平:某中医药院校的医学信息…

Discuz!X 3.4任意文件删除漏洞

复现过程: 1.访问http://x.x.x/robots.txt(文件存在) 2.登录弱口令 账号:admin密码:admin 3.来到个人设置页面找到自己的formhash: 4.点击保存,抓包 来到这个参数:birthprovin…

力扣 -- 879. 盈利计划(二维费用的背包问题)

解题步骤&#xff1a; 参考代码&#xff1a; 未优化的代码&#xff1a; class Solution { public:int profitableSchemes(int n, int minProfit, vector<int>& group, vector<int>& profit) {//计划数int lengroup.size();//每一维都多开一行空间vector&…

websocket逆向【python实现websocket拦截】

python实现websocket拦截 前言一、拦截的优缺点优点:缺点:二、实现方法1.环境配置2.代码三、总结前言 开发者工具F12,筛选ws后,websocket的消息是这样显示的,如何获取这里面的消息呢? 以下是本篇文章正文内容 一、拦截的优缺点 主要讲解一下websocket拦截的实现,现在…

使用Pytorch从零实现Vision Transformer

在这篇文章中,我们将基于Pytorch框架从头实现Vision Transformer模型,并附录完整代码。 Vision Transformer(ViT)是一种基于Transformer架构的深度学习模型,用于处理计算机视觉任务。它将图像分割成小的图像块(patches),然后使用Transformer编码器来处理这些图像块。V…

VC++创建windows服务程序

目录 1.关于windows标准可执行程序和服务程序 2.服务相关整理 2.1 VC编写服务 2.2 服务注册 2.3 服务卸载 2.4 启动服务 2.5 关闭服务 2.6 sc命令 2.7 查看服务 3.标准程序 3.1 后台方式运行标准程序 3.2 查找进程 3.3 终止进程 以前经常在Linux下编写服务器程序…

【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…

Lagrange插值法实验:求拉格朗日插值多项式和对应x的近似值matlab实现(内附代码)

一、实验要求 已知函数表&#xff1a; 求出Lagrange 插值多项式&#xff0c;并计算x1.2处的y的近似值。 二、MATLAB代码 求解多项式&#xff1a; X input(请输入横坐标向量X:\nX); % 获取用户输入的横坐标向量 Y input(请输入纵坐标向量Y:\nY); % 获取用户输入的纵坐标…

Java 基于 SpringBoot+Vue 的留守儿童关爱网站

文章目录 1.研究背景2. 技术栈3.系统分析4系统设计5系统的详细设计与实现5.1系统功能模块5.2管理员功能模块 源码下载地址 1.研究背景 以往的留守儿童爱心的管理&#xff0c;一般都是纸质文件来管理留守儿童爱心信息&#xff0c;传统的管理方式已经无法满足现代人们的需求&…

JVM篇---第三篇

系列文章目录 文章目录 系列文章目录一、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?二、Java内存结构三、说说对象分配规则一、什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”? Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文…

MSTP+VRRP配置

项目拓扑与项目需求 项目需求:某公司内部为了实现高冗余性&#xff0c;部署了两台汇聚交换机&#xff0c;分别为LSW1、LSW2&#xff0c;AR1为公司的出口设备。公司内部有两个部门&#xff0c;分别划分在vlan10和vlan20。现在需要实现以下需求&#xff1a; 由于汇聚层和接入层…

[论文必备]最强科研绘图分析工具Origin(2)——简单使用教程

本篇将介绍Origin的简单使用教程。 安装教程见上篇&#xff1a;[论文必备]最强科研绘图分析工具Origin&#xff08;1&#xff09;——安装教程 目录 &#x1f4e2;一、工具栏介绍 &#x1f4e3;1.1 行 1.1.1 标准栏 1.1.2 导入栏 1.1.3 工作表数据 1.1.4 图表数据 &a…

Linux下基本指令(上)

文章内容&#xff1a; 1. ls 指令 语法&#xff1a; ls [选项][目录或文件] 功能&#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及其他信息。 单个ls显示当前目录下的文件和目录 常用选项&#…

flex布局与几个实例(含源码)

本文简单的说明下flex布局 有源码实例&#xff0c;后续会持续添加 flex默认主轴是横轴 容器主要有6个属性 flex-direction 决定主轴的方向 flex-direction: row | row-reverse | column | column-reverse; flex-wrap 决定是否换行 flex-wrap: nowrap | wrap | wrap-revers…