浏览器渲染原理 - 输入url 回车后发生了什么

2023-10-30

在上一篇文章中,介绍了 浏览器的事件循环,其中提到了浏览器的进程模型。那浏览器是如何渲染页面的呢?

渲染时间点

浏览器会通过网络进程中的线程来通信,获取到 html 数据后生成渲染任务,发送给消息队列。

渲染主线程会执行渲染任务。整个渲染流程:把 html 字符串解析为像素点信息,再交给 GPU来渲染后在页面中展示。

在这里插入图片描述

渲染流水线

在这里插入图片描述

每个阶段都有明确的输入输出,上个阶段的输出会成为下个阶段的输入。形成一套完整的流水线。

1,解析(parse)HTML

会将 html 字符串解析为 2个树。因为字符串不好操作,对象更容易处理。

1.1,DOM树

也就是 document 对象。可以在控制台通过console.dir(document) 展示对象结构。
在这里插入图片描述

1.2,CSSOM树

包括:

在这里插入图片描述

注意,这里的 CSSOM树 ≠ document.styleSheets。因为 document.styleSheets 只包括内部样式外部样式,每写一个 <style><link> 就会多一个 CSSStyleSheet 样式表:

在这里插入图片描述

举例说明:

<html>
  <head>
    <style>
      body h1 {
        color: red;
        font-size: 3em;
      }
      div p {
        margin: 1em;
        color: blue;
      }
    </style>
  </head>
  <body>
    <h1>下雪天的夏风</h1>
    <div>
      <p>求关注</p>
    </div>
  </body>
</html>

在这里插入图片描述

可以看到:

  • CSSStyleSheet 样式表
    • CSSStyleRule 规则对象
      • selectorText 选择器
      • style 样式(键值对)

另外,CSSStyleSheet 样式表是可以直接通过 js 操作的。

举例:通过 js 给页面所有 div 添加 border

document.styleSheets[0].addRule('div', 'border: 1px solid !important')

这样添加样式的方式,一般框架用的多。最终样式不会在内联样式中展现。

1.3,解析时遇到 css 是怎么做的

渲染主线程遇到 css 时,会启动一个预解析线程,让它来率先下载和解析 css。渲染主线程继续解析 html。

预解析线程会快速浏览,如果遇到外部样式link ,会通知网络线程来下载 css,下载完成后进行“解析”完成后交给渲染主线程。

并不是真正的解析,只是做一些前期工作,最终生成 CSSOM 树还是由渲染主线程来完成。

所以,css 代码不会阻塞解析 HTML。

在这里插入图片描述

1.4,解析时遇到 js 是怎么做的

没有生成所谓的 js 树,是因为 js 只需要执行一遍即可。DOM树和CSSOM树作为解析 HTML 的输出,后续还会有其他的操作。

渲染主线程遇到内部 js 时,直接启动 V8 引擎执行即可;遇到外部 js 时,会启动一个预解析线程,让它来下载 js,渲染主线程暂停

预解析线程会通知网络线程来下载 js,下载完成后再交给渲染主线程来执行。执行完继续解析 HTML。

这样做的原因:DOM 树是边解析边生成的,而 js 代码可能会修改之前已解析好的内容。

所以,js 代码会阻塞解析 HTML。

在这里插入图片描述

2,样式计算 Recalculate style

遍历DOM树,计算每个节点的最终样式 Computed Style。

这一过程,许多预设值会变成绝对值,比如 red 变为 rgb(255,0,0);相对单位变为绝对单位,比如rem 变为 px

最终会得到1个带有最终样式的 DOM 树。
在这里插入图片描述

可以在浏览器的 computed 窗口中,或使用 getComputedStyle() 查看某个元素的最终样式。

3,布局 layout

遍历DOM树的每个节点,根据 css 属性值计算每个节点的几何信息(尺寸,相对包含块的位置),生成一个 layout 树。

注意到 DOM 树和 layout 树不一定一一对应。
在这里插入图片描述
举例1:diaplay:none 的元素不会出现在 layout 树中。

问题来了,为什么<head> <link> 等元素默认是隐藏的?因为在浏览器默认样式表中,它们 diaplay:none
在这里插入图片描述
举例2:伪元素的 content 内容在 DOM 树中没有,在 layout 树中有。

在这里插入图片描述
举例3:内容必须在行盒中,行盒和块盒不能相邻。所以在 layout 树中会有匿名块盒

解释:“行级元素”,“块级元素”这些元素指的是 html。但元素的类型是 css 属性决定的。所以称为行盒或块盒。

在这里插入图片描述

4,分层 layer

现在 layout 布局树中每个节点的几何信息,尺寸位置等都明确了。渲染主线程会使用一套策略对整个布局树分层。

目的是提升效率,这样可以让之后页面的修改更新不会影响到其他层。类似 PS 中的图层,修改某一个图层不会影响到其他图层。

可以在浏览器控制台的 Layers 面板查看当前网页的分层信息。
也不会分太多的层,因为会比较占内存。滚动条是单独一层。

在这里插入图片描述

另外,和堆叠上下文有关的 css 属性(transform,opacity)会影响分层的决策。其中 will-change 属性能更大程度的影响分层角色,可能会将设置该属性的元素单独分一层。

因为这个属性会告诉浏览器,我可能会经常变化,浏览器最好掂量下。

5,绘制 paint

分层后,会对每层都生成绘制指令,类似于 canvas 中的 API 一样。其实canvas 用的就是浏览器内核的绘制功能。

指令举例:“笔”移动到 xx 坐标位置,画 100*100 的矩形,并用红色填充等等。

在这里插入图片描述

以上。渲染主线程的工作结束,剩下的步骤交给其他线程来完成。

在这里插入图片描述

6,分块 tiling

将每层都分为多个小的区域,浏览器视窗区域的优先级最高,靠近视窗区域的优先级次之。
在这里插入图片描述

分块逻辑:渲染主线程每个图层的绘制信息交给合成线程,合成线程又会启动多个分块线程来对每个图层进行分块。

合成线程也属于渲染线程

在这里插入图片描述

7,光栅化 raster

将每个块变成位图,位图就是每个像素点的信息。优先处理靠近视窗的块。

位图就是内存中的二位数组,其中记录了每个像素点的信息。

在这里插入图片描述

此过程会用到GPU来加速,用到显卡。
在这里插入图片描述

8,画 draw

合成线程现在拿到了所有的信息,在画之前还需要确认【指引信息 quad】,也就是位图相对的屏幕在哪里。

注意,之前布局树中的信息是相对于整个页面的。现在需要知道每个块相对于屏幕的位置信息。

步骤:合成线程将指引信息 --> GPU 进程 --> 硬件显卡,由显卡来呈现最终的像素信息。

GPU 做中转的原因是:GPU 是浏览器的进程。合成线程属于渲染进程,它是在沙盒中的,与硬件系统做隔离,提升安全性。所以渲染进程是没有调度系统硬件能力的。

在这里插入图片描述
而 css 中的 transform 属性就是在这一步完成的。这就是 transform 效率高的原因,直接跳过之前所有的步骤。

常见面试题

什么是 reflow

【recalculate layoutBlockFlow 重排】它的本质是重新计算 layout 树

当做了会影响 layout 树的操作后,比如修改几何尺寸相关的信息时,会引起重新计算 layout 树。

在这里插入图片描述

并且,为了避免连续多次的操作导致 layout 树反复计算,浏览器会合并这些操作,当 js 代码完成后统一计算。所以这一步骤是异步的。

同样因为这个原因,当 js 获取布局属性时,可能无法获取到最新的布局信息。

浏览器会在反复权衡下,最终决定获取属性时立即 reflow。

什么是 repaint

它的本质是重新根据分层信息计算了绘制指令

当改变了可见样式,比如颜色等和几何尺寸无关的属性时,就需要重新计算,会从【绘制】这一步骤开始重新执行。

而因为几何尺寸也属于可见样式,所以 reflow 一定会引起 repaint。

在这里插入图片描述

为什么 transform 效率高

因为 transform 既不会影响布局,也不会影响绘制,它只会影响渲染的最后一步【画】。而【画】是在合成线程中,不会影响到渲染主线程。同样无论渲染主线程多忙,也不会影响到 transform。

验证代码如下,当渲染主线程卡死时,transform 不受影响。

<!DOCTYPE html>
<html lang="en">
  <head>
    <style>
      .common {
        width: 50px;
        height: 50px;
        background-color: salmon;
        border-radius: 50%;
        margin: 10px;
      }
      .ball1 {
        animation: move1 1s alternate infinite;
      }
      .ball2 {
        position: relative;
        left: 0;
        animation: move2 1s alternate infinite;
      }
      @keyframes move1 {
        to {
          transform: translate(100px);
        }
      }
      @keyframes move2 {
        to {
          left: 100px;
        }
      }
    </style>
  </head>
  <body>
    <button id="btn">死循环3s</button>
    <div class="common ball1"></div>
    <div class="common ball2"></div>
    <script>
      btn.addEventListener("click", function () {
        delay(3000);
      });
      function delay(duration) {
        var start = Date.now();
        while (Date.now() - start < duration) {}
      }
    </script>
  </body>
</html>

效果:
在这里插入图片描述

滚动也是一样的逻辑,如果 js 有一段上面的死循环,并不会影响到滚动。因为只有视窗内元素的位置变了,直接执行【画 draw】这一步骤。

DOMContentLoaded 事件和 load 事件的区别。

1,当 DOM 树完成生成好后,触发DOMContentLoaded事件

document.addEventListener("DOMContentLoaded", function () {
  console.log("DOM树加载完成");
});

2,当页面所有外部资源全部加装完成后,触发 load 事件

window.onload = function() {
  console.log('所有资源加载完成');
}

load 事件也可以针对单个外部资源使用。

当遇到外部资源会下载,但并不阻塞 DOM 树的生成。

以上。


参考:渡一教育。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

浏览器渲染原理 - 输入url 回车后发生了什么 的相关文章

  • 使用 vue.js 显示 json 结果

    您好 我尝试使用 vue js 显示 json 文件结果 目标是结果将显示在值上 这是我的代码 data return fetchData function var self this self http get api casetotal
  • 如何在 guildMemberAdd 中使用awaitReactions

    当用户连接到我的服务器时 我向他们发送消息 并且我想通过单击反应来继续授权 我怎样才能创建这个 我正在使用以下代码 robot on guildMemberAdd gMembAdd gt gMembAdd send Hi gMembAdd
  • 使用 JS 和 HTML 将当前 URL 插入链接

    所以 我已经阅读了类似的内容 但我仍然找不到更适合我正在做的事情的答案 我正在尝试使用 JS 获取当前页面 URL 并将其附加到社交媒体共享链接 如下所示 a href target blank 使用 Javascript 我成功地将当前
  • 如何获取 CSS 旋转元素的实际(非原始)高度

    我需要获取几个不同元素的实际高度 为了精确的自定义工具提示定位 并且其中一些元素 不是全部 被旋转 elem outerHeight 返回原始高度 而不是实际显示的高度 这是一个非常简单的例子 http jsfiddle net NPC42
  • 使用西里尔字母的正则表达式

    我有一个用于文本区域字段中字数统计的 jQuery 函数 此外 它排除所有用 三重括号 封闭的单词 它对于拉丁字符效果很好 但对于西里尔字母句子有问题 我认为错误部分与正则表达式有关 field val replace g match b
  • CSS悬停边框而不调整图像大小

    我想问一下 在不调整图像大小的情况下 我的悬停边框下方功能做错了什么 我已按照给出的指南进行操作here https css tricks com image rollover borders that do not change layo
  • navigator.platform 在 ARM Mac 上的价值是什么?

    苹果有released https www apple com apple events november 2020 几款基于采用 ARM 架构的 M1 芯片的新计算机 与之前基于 x86 架构的计算机相比 的价值是多少navigator
  • 所有属性的 JavaScript getter

    长话短说 我现在的情况是想要一个 PHP 风格的 getter 但是是 JavaScript 的 我的 JavaScript 仅在 Firefox 中运行 因此 Mozilla 特定的 JS 对我来说没问题 我能找到的制作 JS gette
  • Node.JS Web 服务器中的安全性

    所以 我正在学习 Node JS 到目前为止我很喜欢它 我已经有几个项目在工作了 我想我可以在其中使用nodejs 不过 我担心安全问题 如果我使用 Node JS http 模块编写自定义 Web 服务器 我是否可能非常容易受到攻击 Ap
  • 更改导航栏悬停时 div 的背景图像

    我正在开发一个项目 我对 Javascript 很陌生 所以我想知道是否有 Jquery 代码或只是一个关于如何使背景图像在导航菜单悬停时更改的过程 例如将鼠标悬停在链接一上会将 div 的背景图像更改为图像 1 将鼠标悬停在链接二上会将
  • 如何使 Loopback 模型事件起作用?

    我尝试过一个例子http apidocs strongloop com loopback model http apidocs strongloop com loopback model MyModel on changed functio
  • JavaScript 将 NULL 转换为 0

    我正在使用 jQuery 来获取元素的高度 但如果该元素不存在 以下代码将返回 NULL height menu li active ul height returns integer or null 这是一种跨浏览器安全的方法 可以使用以
  • JavaScript 中的自定义“确认”对话框?

    我一直在开发一个使用自定义 模式对话框 的 ASP net 项目 我在这里使用吓人引号 因为我知道 模式对话框 只是我的 html 文档中的一个 div 它被设置为出现在文档其余部分的 顶部 而不是真正意义上的模式对话框 在网站的许多部分
  • 按位非运算符

    为什么要按位运算 0 打印 1 在二进制中 不是0应该是1 为什么 你实际上很接近 在二进制中 不是0应该是1 是的 当我们谈论一位时 这是绝对正确的 然而 一个int其值为0的实际上是32位全零 将所有 32 个 0 反转为 32 个 1
  • 如何检查 URL 末尾是否有特定字符串

    我需要根据 URL 末尾的内容让覆盖层向下滑动 如果 URL 末尾有 faq 覆盖层下降 如何在 jQuery JavaScript 中做到这一点 如果您的网址看起来像这样http yourdomain com faq 你可以这样做 var
  • 使用 CSS 内容添加 HTML 实体

    你如何使用CSS content要添加的属性HTML实体 使用这样的东西只是打印 nbsp 到屏幕而不是不间断空格 breadcrumbs a before content nbsp 您必须使用转义的 unicode Like breadc
  • 将回调函数与原型函数一起使用

    在执行回调时 我无法弄清楚如何传递对象方法而不是排序 通用原型 方法 function Client this name hello Client prototype apiCall function method params callb
  • 如何在 C# 中通过 JavaScript 回调运行 QUnit 测试并获取测试结果?

    在我的几个项目中 我使用 MVC 模式将代码 关注点 分为 3 层 模型层和控制层都在 C 上运行 因此我使用 MSTest 或 NUnit 等测试框架来验证这些层的功能需求 对于视图层 我使用 QUnit 来测试 JavaScript 文
  • 错误:Javascript 上的 [object Object]

    当我在 Firebug 中运行下面的 javascript 时 我不断收到错误 我已经尝试更改多项内容 但它仍然输出错误 我正在使用 api 从 XML 检索信息 然后将其输出到屏幕上 但我不断收到对象错误 有人能看出为什么吗 任何帮助表示
  • 如何通过 jQuery onblur 提交表单

    所以我尝试通过 jQuery onblur 提交表单 即一旦焦点离开密码字段 表单就会通过 jQuery 提交 有类似的问题 但这不是我要找的 我尝试使用 document getElementById 但它不起作用 任何帮助表示赞赏 提前

随机推荐

  • usb:认识usb传输(一)

    文章目录 一 usb发展背景 1 usb特点 2 usb发展 1 更名 2 发展 3 传输速度 4 usb编 解码方式 反向不归零 NRZI 位填充 5 信号传输状态 5 帧 6 通讯过程划分 二 usb的四种传输 1 控制传输 2 中断传
  • UE4鼠标滚轮控制镜头缩放

    蓝图 因为其实实现起来比较简单 所以直接上蓝图 主要是用到了UE4的鼠标滚轮操作映射 每当滑动鼠标滚轮的时候就会传出一个数值 有正有负有0 然后将角色的相机摇臂组件拖进蓝图 获取其中的Target Arm Length变量加上一个数值再重新
  • postman实战:2.参数化csv和json

    在上节课讲解了使用postman做接口测试时 如何设置环境变量和全局变量关联参数 关联环境变量和全局变量中参数时 他们的作用范围再来回顾一下 下面来看一下postman里面关于参数化的应用 首先分析下应用场景 1 某一个接口我们对入参设计了
  • 十二、支持向量机

    def svmm 支持向量机 完善 用超平面对高纬空间中的样本进行分类 为了解决线性不可分问题 引入了核函数 常用核函数有线性核函数 多项式核函数 高斯核函数和sigmoid核函数 API sklearn svm SVC C 1 0 ker
  • 机器学习综述论文笔记:Machine Learning: A Review of Learning Types

    机器学习review Paper Machine Learning A Review of Learning Types 这是一篇关于机器学习的综述 里面简述了各种现有的机器学习技术 1 主要的方法 监督 无监督 强化 1 1 监督学习 数
  • Redis原理篇(二)网络模型

    一 用户空间和内核空间 应用需要通过Linux内核与硬件交互 内核本质也是应用 运行的时候也需要CPU资源 内存资源 用户应用也在消耗这些资源 为了避免用户应用导致冲突甚至内核崩溃 用户应用与内核是分离的 进程的寻址空间会划分为两部分 内核
  • 公司前端vue是用vscode开发工具写的,个人喜欢用idea,但是idea在保存代码的时候会自动去除代码行最后的空格,造成不该修改的地方修改了,影响代码提交

    取消这个功能 File Settings Editor General On Save Remave trailing
  • springBoot 观察者模式

    观察者设计模式 jie神说用订阅和发布来理解更好 我想了一下是的 为什么呢 因为监听器这个名词听起来是一个主动的 可实际监听器是一个被动的玩意 比如我们事件源发布一个事件 然后监听器订阅了这个事件就能做出动作 里面涉及到三个对象 事件源 事
  • 【2023】JAVA和PLC实现通讯读取写入数据,以三菱PLC举例

    1 创建maven工程引入依赖
  • --- Error: User Command terminated, Exit-Code = 1解决办法

    使用keil MDK编译项目时 compiling编译通过 但是文件最后出现错误 Error User Command terminated Exit Code 1 经查阅资料 MDK需要fromelf exe文件生成 bin 那么在重新安
  • uboot编译报错解决

    uboot编译报错 root ubuntu home gjt uboot u boot 2015 01 make scripts kconfig conf silentoldconfig Kconfig scripts kconfig co
  • Protobuf 使用(c++)

    一 Protobuf 安装 安装protobuf tar xvf protobuf cd protobuf autogen sh生成configure configure prefix usr local protobuf make mak
  • 用P5.js实现一个动态的绘画系统

    摘要 通过一段时间的学习 我发现码绘的可能性比我想象的要更大 我们可以用码绘实现很多手绘很难达到的效果 比如创作一幅会动的 能进行交互的画作 如何通过类似画笔的东西在屏幕上创作出时刻在改变的 并且我们可以进行实时修改的像动画一样的作品 这就
  • AutoSchedule和AutoTVM

    简介 AutoTVM 用户自己手写一个模版 在模版里面自己定义一下tune的参数 例如tile size等 给定一个模版 在这个模版里面去搜索参数 使得可以达到一组最好的参数使得张量计算的结果最好 但是 它是一种基于模板的方法 因此仍然需要
  • 构建一个Flex程序

    构建一个Flex程序 Flex定义了一个基于组件的开发模型 从而我们可以用来构建我们的程序 为了高效的设计与构建我们的程序 我们应该熟悉这个模型 以及程序开发步骤与布署过程 在这一章描述了我们用来创建一个程序的开发过程 在这一章所包含的如下
  • Neo4j的安装和简单使用

    1 首先是Neo4j的下载和安装 下载地址 https neo4j com download 我下载的是Community Edition 下载完毕 因为是 exe文件 直接双击安装即可 没有什么需要注意的 2 安装完毕 在第一次使用Neo
  • SAP ABAP 粘贴板负号前置

    场景 用户一般Ctrl C复制ALV数据到Excel处理 如果有负数的数值 负号在数值的后面 Excel不认识 要费劲巴拉的一个个改正 苦不堪言 本程序功能 在ALV复制数据后 直接更改剪贴板里面的数据 把负号提到前面 然后直接在Excel
  • 立创商城中元器件封装的3d模型导出STEP格式文件

    1 首先安装FreeCAD软件和注册立创账号 2 进入立创EDA专业版 同时登录立创EDA账号 立创EDA专业版网址 https pro lceda cn editor 3 新建一个工程并打开 4 把立创商城的商品编号复制到下面的元器件库中
  • Python 实现的关键词查找小工具

    引言 平时工作时 有时会遇到这样的情景 在一个目录及其子目录下所有的文本文件中查找某个关键字 词或者完整的句子 当然 如果是在Linux平台上 find egrep就能实现这样的功能 不过最近学习了Python tkinter相关的知识 自
  • 浏览器渲染原理 - 输入url 回车后发生了什么

    目录 渲染时间点 渲染流水线 1 解析 parse HTML 1 1 DOM树 1 2 CSSOM树 1 3 解析时遇到 css 是怎么做的 1 4 解析时遇到 js 是怎么做的 2 样式计算 Recalculate style 3 布局