当前位置: 首页 > article >正文

【极客时间】浏览器工作原理与实践-2 宏观视角下的浏览器 (6讲) - 2.6 渲染流程(下):HTML、CSS和JavaScript,是如何变成页面的?

https://time.geekbang.org/column/article/118826

2.6 渲染流程(下):HTML、CSS和JavaScript,是如何变成页面的?

2.5介绍了渲染流水线中的 DOM 生成、样式计算和布局三个阶段,2.6讲解渲染流水线后面的阶段。

简单回顾下2.5前三个阶段的主要内容:

  • 在 HTML 页面内容被提交给渲染引擎之后,渲染引擎首先将 HTML 解析为浏览器可以理解的 DOM;
  • 然后根据 CSS 样式表,计算出 DOM 树所有节点的样式;
  • 接着又计算每个元素的几何坐标位置,并将这些信息保存在布局树中。

分层

有了布局树,而且每个元素的具体位置信息都计算出来了,那么接下来是不是就要开始着手绘制页面了?

答案依然是否定的。

因为页面中有很多复杂的效果,如一些复杂的 3D 变换、页面滚动,或者使用 z-indexing 做 z 轴排序等,为了更加方便地实现这些效果,渲染引擎还需要为特定的节点生成专用的图层,并生成一棵对应的图层树(LayerTree)。如果你熟悉 PS,相信你会很容易理解图层的概念,正是这些图层叠加在一起构成了最终的页面图像。

要想直观地理解什么是图层,你可以打开 Chrome 的“开发者工具”,选择“Layers”标签,就可以可视化页面的分层情况,如下图所示:

在这里插入图片描述
从上图可以看出,渲染引擎给页面分了很多图层,这些图层按照一定顺序叠加在一起,就形成了最终的页面,你可以参考下图:

在这里插入图片描述

现在你知道了浏览器的页面实际上被分成了很多图层,这些图层叠加后合成了最终的页面。下面我们再来看看这些图层和布局树节点之间的关系,如文中图所示:

在这里插入图片描述
通常情况下,并不是布局树的每个节点都包含一个图层,如果一个节点没有对应的层,那么这个节点就从属于父节点的图层

如上图中的 span 标签没有专属图层,那么它们就从属于它们的父节点图层。

但不管怎样,最终每一个节点都会直接或者间接地从属于一个层。

那么需要满足什么条件,渲染引擎才会为特定的节点创建新的图层呢?

通常满足下面两点中任意一点的元素就可以被提升为单独的一个图层。

第一点,拥有层叠上下文属性的元素会被提升为单独的一层

页面是个二维平面,但是层叠上下文能够让 HTML 元素具有三维概念,这些 HTML 元素按照自身属性的优先级分布在垂直于这个二维平面的 z 轴上。你可以结合下图来直观感受下:
在这里插入图片描述

从图中可以看出,明确定位属性的元素、定义透明属性的元素、使用 CSS 滤镜的元素等,都拥有层叠上下文属性。若你想要了解更多层叠上下文的知识,你可以参考这篇文章。

第二点,需要剪裁(clip)的地方也会被创建为图层

不过首先你需要了解什么是剪裁,结合下面的 HTML 代码:

<style>div {width: 200;height: 200;overflow:auto;background: gray;} 
</style>
<body><div ><p>所以元素有了层叠上下文的属性或者需要被剪裁,那么就会被提升成为单独一层,你可以参看下图:</p><p>从上图我们可以看到,document层上有A和B层,而B层之上又有两个图层。这些图层组织在一起也是一颗树状结构。</p><p>图层树是基于布局树来创建的,为了找出哪些元素需要在哪些层中,渲染引擎会遍历布局树来创建层树(Update LayerTree)。</p> </div>
</body>

在这里我们把 div 的大小限定为 200 * 200 像素,而 div 里面的文字内容比较多,文字所显示的区域肯定会超出 200 * 200 的面积,这时候就产生了剪裁,渲染引擎会把裁剪文字内容的一部分用于显示在 div 区域,下图是运行时的执行结果:

在这里插入图片描述
出现这种裁剪情况的时候,渲染引擎会为文字部分单独创建一个层,如果出现滚动条,滚动条也会被提升为单独的层。你可以参考下图:

在这里插入图片描述

所以说,元素有了层叠上下文的属性或者需要被剪裁,满足其中任意一点,就会被提升成为单独一层。

图层绘制

在完成图层树的构建之后,渲染引擎会对图层树中的每个图层进行绘制,那么接下来我们看看渲染引擎是怎么实现图层绘制的?

试想一下,如果给你一张纸,让你先把纸的背景涂成蓝色,然后在中间位置画一个红色的圆,最后再在圆上画个绿色三角形。你会怎么操作呢?

通常,你会把你的绘制操作分解为三步:

  • 绘制蓝色背景;
  • 在中间绘制一个红色的圆;
  • 再在圆上绘制绿色三角形。

渲染引擎实现图层的绘制与之类似,会把一个图层的绘制拆分成很多小的绘制指令,然后再把这些指令按照顺序组成一个待绘制列表,如下图所示:

在这里插入图片描述

从图中可以看出,绘制列表中的指令其实非常简单,就是让其执行一个简单的绘制操作,比如绘制粉色矩形或者黑色的线等。而绘制一个元素通常需要好几条绘制指令,因为每个元素的背景、前景、边框都需要单独的指令去绘制。所以在图层绘制阶段,输出的内容就是这些待绘制列表

你也可以打开“开发者工具”的“Layers”标签,选择“document”层,来实际体验下绘制列表,如下图所示:

在这里插入图片描述

在该图中,区域 1 就是 document 的绘制列表,拖动区域 2 中的进度条可以重现列表的绘制过程。

栅格化(raster)操作

绘制列表只是用来记录绘制顺序和绘制指令的列表,而实际上绘制操作是由渲染引擎中的合成线程来完成的。你可以结合下图来看下渲染主线程和合成线程之间的关系:

在这里插入图片描述
如上图所示,当图层的绘制列表准备好之后,主线程会把该绘制列表提交(commit)给合成线程,那么接下来合成线程是怎么工作的呢?

那我们得先来看看什么是视口,你可以参看下图:
在这里插入图片描述

通常一个页面可能很大,但是用户只能看到其中的一部分,我们把用户可以看到的这个部分叫做视口(viewport)

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

基于这个原因,合成线程会将图层划分为图块(tile),这些图块的大小通常是 256x256 或者 512x512,如下图所示:

在这里插入图片描述
然后合成线程会按照视口附近的图块来优先生成位图,实际生成位图的操作是由栅格化来执行的。所谓栅格化,是指将图块转换为位图。而图块是栅格化执行的最小单位。渲染进程维护了一个栅格化的线程池,所有的图块栅格化都是在线程池内执行的,运行方式如下图所示:

在这里插入图片描述

通常,栅格化过程都会使用 GPU 来加速生成,使用 GPU 生成位图的过程叫快速栅格化,或者 GPU 栅格化,生成的位图被保存在 GPU 内存中。

相信你还记得,GPU 操作是运行在 GPU 进程中,如果栅格化操作使用了 GPU,那么最终生成位图的操作是在 GPU 中完成的,这就涉及到了跨进程操作。具体形式你可以参考下图:

从图中可以看出,渲染进程把生成图块的指令发送给 GPU,然后在 GPU 中执行生成图块的位图,并保存在 GPU 的内存中。

从图中可以看出,渲染进程把生成图块的指令发送给 GPU,然后在 GPU 中执行生成图块的位图,并保存在 GPU 的内存中。

合成和显示

一旦所有图块都被光栅化,合成线程就会生成一个绘制图块的命令——“DrawQuad”,然后将该命令提交给浏览器进程

浏览器进程里面有一个叫== viz 的组件,用来接收合成线程发过来的 DrawQuad 命令==,然后根据 DrawQuad 命令,将其页面内容绘制到内存中,最后再将内存显示在屏幕上

到这里,经过这一系列的阶段,编写好的 HTML、CSS、JavaScript 等文件,经过浏览器就会显示出漂亮的页面了。

渲染流水线大总结

好了,我们现在已经分析完了整个渲染流程,从 HTML 到 DOM、样式计算、布局、图层、绘制、光栅化、合成和显示。下面我用一张图来总结下这整个渲染流程:

在这里插入图片描述

结合上图,一个完整的渲染流程大致可总结为如下:

  • 渲染进程将 HTML 内容转换为能够读懂的 DOM 树结构。
  • 渲染引擎将 CSS 样式表转化为浏览器可以理解的 styleSheets,计算出 DOM 节点的样式。
  • 创建布局树,并计算元素的布局信息。
  • 对布局树进行分层,并生成分层树。
  • 为每个图层生成绘制列表,并将其提交到合成线程。
  • 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图。
  • 合成线程发送绘制图块命令 DrawQuad 给浏览器进程。
  • 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上。

相关概念

有了上面介绍渲染流水线的基础,我们再来看看三个和渲染流水线相关的概念——“重排”“重绘”和“合成”。理解了这三个概念对于你后续 Web 的性能优化会有很大帮助。

1. 更新了元素的几何属性(重排)

在这里插入图片描述

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

2. 更新元素的绘制属性(重绘)

接下来,我们再来看看重绘,比如通过 JavaScript 更改某些元素的背景颜色,渲染流水线会怎样调整呢?你可以参考下图:

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

3. 直接合成阶段

那如果你更改一个既不要布局也不要绘制的属性,会发生什么变化呢?渲染引擎将跳过布局和绘制,只执行后续的合成操作,我们把这个过程叫做合成。具体流程参考下图:

在这里插入图片描述

在上图中,我们使用了 CSS 的 transform 来实现动画效果,这可以避开重排和重绘阶段,直接在非主线程上执行合成动画操作。这样的效率是最高的,因为是在非主线程上合成,并没有占用主线程的资源,另外也避开了布局和绘制两个子阶段,所以相对于重绘和重排,合成能大大提升绘制效率。

至于如何用这些概念去优化页面,我们会在后面相关章节做详细讲解的,这里你只需要先结合“渲染流水线”弄明白这三个概念及原理就行。

总结

在这里插入图片描述

通过本文的分析,你应该可以看到,Chrome 的渲染流水线还是相当复杂晦涩,且难以理解,不过 Chrome 团队在不断添加新功能的同时,也在不断地重构一些子阶段,目的就是让整体渲染架构变得更加简单和高效,正所谓大道至简

通过这么多年的生活和工作经验来看,无论是做架构设计、产品设计,还是具体到代码的实现,甚至处理生活中的一些事情,能够把复杂问题简单化的人都是具有大智慧的。所以,在工作或生活中,你若想要简化遇到的问题,就要刻意地练习,练就抓住问题本质的能力,把那些复杂的问题简单化,从而最终真正解决问题。

思考时间

在优化 Web 性能的方法中,减少重绘、重排是一种很好的优化方式,那么结合文中的分析,你能总结出来为什么减少重绘、重排能优化 Web 性能吗?那又有那些具体的实践方法能减少重绘、重排呢?

相关文章:

【极客时间】浏览器工作原理与实践-2 宏观视角下的浏览器 (6讲) - 2.6 渲染流程(下):HTML、CSS和JavaScript,是如何变成页面的?

https://time.geekbang.org/column/article/118826 2.6 渲染流程&#xff08;下&#xff09;&#xff1a;HTML、CSS和JavaScript&#xff0c;是如何变成页面的&#xff1f; 2.5介绍了渲染流水线中的 DOM 生成、样式计算和布局三个阶段&#xff0c;2.6讲解渲染流水线后面的阶段…...

浏览器WEB播放RTSP

注意&#xff1a;浏览器不能直接播放RTSP&#xff0c;必须转换后才能播放。这一点所有的播放都是如此。 参考 https://github.com/kyriesent/node-rtsp-stream GitHub - phoboslab/jsmpeg: MPEG1 Video Decoder in JavaScript 相关文件方便下载 https://download.csdn.net…...

DeepSeek开源Day4:DualPipeEPLB技术详解

2 月 24 日&#xff0c;DeepSeek 启动 “开源周”&#xff0c;第四个开源的代码库为 DualPipe 与 EPLB&#xff08;一下发布了两个&#xff09;。DualPipe 与 EPLB 依然使用了大量与 Hopper 架构绑定的技术。 DualPipe 是由 DeepSeek-AI 团队开发的一种双向流水线并行通信算法&…...

阿里推出全新推理模型(因果语言模型),仅1/20参数媲美DeepSeek R1

阿里Qwen 团队正式发布了他们最新的研究成果——QwQ-32B大语言模型&#xff01;这款模型不仅名字萌萌哒(QwQ)&#xff0c;实力更是不容小觑&#xff01;&#x1f60e; QwQ-32B 已在 Hugging Face 和 ModelScope 开源&#xff0c;采用了 Apache 2.0 开源协议。大家可通过 Qwen C…...

vue实现一个pdf在线预览,pdf选择文本并提取复制文字触发弹窗效果

[TOC] 一、文件预览 1、安装依赖包 这里安装了disjs-dist2.16版本&#xff0c;安装过程中报错缺少worker-loader npm i pdfjs-dist2.16.105 worker-loader3.0.8 2、模板部分 <template><div id"pdf-view"><canvas v-for"page in pdfPages&qu…...

Unity自定义渲染管线(Scriptable Render Pipeline)架构设计与实现指南

一、SRP技术体系概述 1. 核心设计理念 全托管渲染控制&#xff1a;通过C#脚本完全掌控渲染流程 模块化架构&#xff1a;将渲染流程拆分为可组合的RenderPass GPU友好设计&#xff1a;支持CommandBuffer与Compute Shader混合编程 对惹&#xff0c;这里有一个游戏开发交流小组…...

时间复杂度分析与递归,以新南UNSW的COMP2521作业题为例

作者&#xff1a;Smooth&#xff08;连接教育高级讲师&#xff09; 首发于&#xff1a;⁠⁠⁠⁠⁠⁠⁠UNSW学习知识库&#xff08;UNSW Study Wiki&#xff09; 创作时间&#xff1a;2025年3月5日 如何测度算法的时间性能&#xff1f;理论分析Theoretical Analysis 测度算法时…...

RxJS与Redux革命性协同:打造高效、解耦的前端状态管理方案

摘要 本文探讨RxJS与Redux的融合应用&#xff0c;通过真实用户场景揭示其必要性&#xff0c;系统讲解整合策略&#xff0c;从基础到高级涵盖响应式编程优化、异步操作处理、状态流控制等核心主题。提供可落地的架构方案与性能优化技巧&#xff0c;包含10可复用代码示例和20实战…...

蓝桥杯P1259-奇怪的馈赠 (贪心题解)

题目&#xff1a;奇怪的捐赠 题目来源&#xff1a;1.奇怪的捐赠 - 蓝桥云课 题目描述 需要将 100 万&#xff08;1,000,000&#xff09;正好分成若干个 7 的次方形式的数&#xff08;如 7^01, 7^17, 7^249 等&#xff09;&#xff0c;且每种金额&#xff08;即每个 7 的次方…...

todo: 使用融云imserve做登录(android)

使用融云做登录注册思路 注册界面需要name, email, password考虑到融云注册用户的post格式 POST http://api.rong-api.com/user/getToken.json?userId1690544550qqcom&nameIronman这里的userId可以使用用户的email&#xff0c;但是要截断和 . 符号&#xff0c;即1690544…...

如何设置爬虫的User-Agent?

在爬虫开发中&#xff0c;设置合适的 User-Agent 是非常重要的一步。User-Agent 是 HTTP 请求头中的一个字段&#xff0c;用于标识客户端&#xff08;通常是浏览器&#xff09;的类型、版本、操作系统等信息。通过设置 User-Agent&#xff0c;可以模拟正常的浏览器访问行为&…...

C++ 二叉搜索树代码

C 二叉搜索树代码 #include <iostream> using namespace std;template<typename T> struct TreeNode{T val;TreeNode *left;TreeNode *right;TreeNode():val(0), left(NULL), right(NULL){}TreeNode(T x):val(x), left(NULL), right(NULL){} };template<typena…...

OpenAI Whisper:开启语音转文本的智能时代

在人工智能技术飞速发展的今天,OpenAI推出的Whisper语音识别系统正悄然改变着人类与机器的交互方式。作为一款开源的AI驱动语音转文本工具,Whisper凭借其跨语言能力、高精度识别和灵活的生态系统,成为开发者和普通用户共同追捧的技术标杆。 核心技术与突破 Whisper基于深度…...

基于CSDN资源,搭建AI赋能农业典型场景落地方案

农业场景&#xff0c;不但是信息化、自动化等薄弱的产业&#xff0c;更是AI落地困难的场景。基于此&#xff0c;想通过这篇文章查找一个CSDN相关资源&#xff0c;论证一下AI赋能农业三个典型场景的实现思路。 场景1&#xff1a;水质-土壤智能调控 **痛点&#xff1a;**水质恶…...

python量化交易——金融数据管理最佳实践——使用qteasy大批量自动拉取金融数据

文章目录 使用数据获取渠道自动填充数据QTEASY数据拉取功能数据拉取接口refill_data_source()数据拉取API的功能特性多渠道拉取数据实现下载流量控制实现错误重试日志记录其他功能 qteasy是一个功能全面且易用的量化交易策略框架&#xff0c; Github地址在这里。使用它&#x…...

RoboBrain:从抽象到具体的机器人操作统一大脑模型

25年2月来自北大、北京智源、中科院自动化所等的论文“RoboBrain: A Unified Brain Model for Robotic Manipulation from Abstract to Concrete”。 目前的多模态大语言模型&#xff08;MLLM&#xff09; 缺少三项必备的机器人大脑能力&#xff1a;规划能力&#xff0c;将复杂…...

DeepSeek本地接口调用(Ollama)

前言 上篇博文&#xff0c;我们通过Ollama搭建了本地的DeepSeek模型&#xff0c;本文主要是方便开发人员&#xff0c;如何通过代码或工具&#xff0c;通过API接口调用本地deepSeek模型 前文&#xff1a;DeepSeek-R1本地搭建_deepseek 本地部署-CSDN博客 注&#xff1a;本文不仅…...

数据库索引的作用:提升数据检索效率的关键

在数据库管理系统中&#xff0c;数据如同浩瀚海洋中的宝藏&#xff0c;如何快速准确地找到所需信息&#xff0c;成为了一个关键问题。这时候&#xff0c;数据库索引就如同一张精确的航海图&#xff0c;指引着我们高效地定位数据。那么&#xff0c;数据库索引究竟是什么&#xf…...

高效便捷的 Spring Boot 通用控制器框架

✨高效便捷的 Spring Boot 通用控制器框架✨ 一、简介 在 Java 开发中&#xff0c;重复性的基础接口编写工作常令人头疼。本框架基于 Spring Boot 与 MyBatis-Plus&#xff0c;精心构建通用控制器类BaseController&#xff0c;旨在为开发者排忧解难&#xff0c;极大减少繁琐的…...

SQL_语法

1 数据库 1.1 新增 create database [if not exists] 数据库名; 1.2 删除 drop database [if exists] 数据库名; 1.3 查询 (1) 查看所有数据库 show databases; (2) 查看当前数据库下的所有表 show tables; 2 数据表 2.1 新增 (1) 创建表 create table [if not exists…...

在 CentOS 上,常用几种方法来确保 Python 脚本在断开终端后继续运行

在 CentOS 上&#xff0c;你可以使用以下几种方法来确保 Python 脚本在断开终端后继续运行&#xff1a; 1. 使用 nohup 命令 nohup 命令可以让进程在终端关闭后继续运行。 nohup python main.py > output.log 2>&1 &nohup&#xff1a;忽略挂断信号&#xff0c…...

全面回顾复习——C++语法篇1(基于牛客网C++题库)

注&#xff1a;牛客网允许使用万能头文件#include<bits/stdc.h> 1、求类型长度——sizeof&#xff08;&#xff09;函数 2、将浮点数四舍五入——round&#xff08;&#xff09;函数——前面如果加上static_cast会更安全一些 在C语言中可以使用printf&#xff08;“.0l…...

一、数据库 MySQL 基础学习 (上)

一、数据库的概念 DB 数据库&#xff08;database&#xff09;&#xff1a;存储数据的“仓库”&#xff0c;保存一系列有组织的数据 DBMS&#xff1a;数据库管理系统(Database Management System)。数据库是通过 DBMS 创建和操作的容器 创建的 DBMS&#xff1a; MySQL、Oracl…...

基于Django创建一个WEB后端框架(DjangoRestFramework+MySQL)流程

一、Django项目初始化 1.创建Django项目 Django-admin startproject 项目名 2.安装 djangorestframework pip install djangorestframework 解释: Django REST Framework (DRF) 是基于 Django 框架的一个强大的 Web API 框架&#xff0c;提供了多种工具和库来构建 RESTf…...

AutoGen学习笔记系列(七)Tutorial - Managing State

这篇文章瞄准的是AutoGen框架官方教程中的 Tutorial 章节中的 Managing State 小节&#xff0c;主要介绍了如何对Team内的状态管理&#xff0c;特别是如何 保存 与 加载 状态&#xff0c;这对于Agent系统而言非常重要。 官网链接&#xff1a;https://microsoft.github.io/auto…...

Redis渐进式遍历数据库

目录 渐进式遍历 数据库 渐进式遍历 keys*可以一次性的把整个redis中所有key都获取到&#xff0c;这个操作是非常危险的&#xff0c;因为可能一下获取到太多的key&#xff0c;阻塞redis服务器。要想很好的获取到所有的key&#xff0c;又不想出现卡死的情况&#xff0c;就可以…...

机器学习中的线性代数:奇异值分解 SVD

线性代数 奇异值分解&#xff08;SVD&#xff09; 参考资料&#xff1a; 超详细&#xff01;彻底搞懂矩阵奇异值分解&#xff08;SVD&#xff09;本质计算应用&#xff01;_哔哩哔哩_bilibili 非常好的视频&#xff0c;本文内容主要来自于该视频&#xff0c;在此表示感谢&#…...

【每日八股】计算机网络篇(三):IP

目录 DNS 查询服务器的基本流程DNS 采用 TCP 还是 UDP&#xff0c;为什么&#xff1f;默认使用 UDP 的原因需要使用 TCP 的场景&#xff1f;总结 DNS 劫持是什么&#xff1f;解决办法&#xff1f;浏览器输入一个 URL 到显示器显示的过程&#xff1f;URL 解析TCP 连接HTTP 请求页…...

6. PromQL的metric name(在node exporter复制下来交给AI解释的)

目录 前言&#xff1a; Go 运行时指标&#xff1a; Go 内存统计指标&#xff1a; CPU 指标&#xff1a; 内存指标&#xff1a; 磁盘指标&#xff1a; 网络指标&#xff1a; 系统指标&#xff1a; 前言&#xff1a; 写这个得目的是为了后续方便查询&#xff0c;因为在pro…...

基于单片机的速度里程表设计(论文+源码)

1 系统方案 本次智能速度里程表的总体架构如图2-1所示&#xff0c;在硬件上包括了STC89C52单片机&#xff0c;电机&#xff0c;显示模块&#xff0c;报警模块&#xff0c;DS1302时钟模块&#xff0c;超速检测模块&#xff0c;按键等等。在软件设计功能的功能上&#xff0c;按下…...