普通视图

发现新文章,点击刷新页面。
昨天 — 2025年11月28日首页

🔄记住这张图,脑子跟着浏览器的事件循环(Event Loop)转起来了

作者 vilan_微澜
2025年11月28日 15:01

一、前言

下面按照我的理解,纯手工画了一张在浏览器执行JavaScript代码的Event Loop(事件循环) 流程图。

后文会演示几个例子,把示例代码放到这个流程图演示其执行流程。

当然,这只是简单的事件循环流程,不过,却能让我们快速掌握其原理。

Event Loop.png

二、概念

事件循环JavaScript为了处理单线程执行代码时,能异步地处理用户交互、网络请求等任务 (异步Web API),而设计的一套任务调度机制。它就像一个永不停止的循环,不断地检查(结合上图就是不断检查Task QueueMicrotask Queue这两个队列)并需要运行的代码。


三、为什么需要事件循环

JavaScript是单线程的,这意味着它只有一个主线程来执行代码。如果所有任务(比如一个耗时的计算、一个网络请求)都同步执行,那么浏览器就会被卡住,无法响应用户的点击、输入,直到这个任务完成。这会造成极差的用户体验。

事件循环就是为了解决这个问题而生的:它让耗时的操作(如网络请求、文件读取)在后台异步执行,等这些操作完成后,再通过回调的方式来执行相应的代码,从而不阻塞主线程

四、事件循环流程图用法演示

演示一:小菜一碟

先来一个都是同步代码的小菜,先了解一下前面画的流程图是怎样在调用栈当中执行JavaScript代码的。

console.log(1)

function funcOne() {
  console.log(2)
}

function funcTwo() {
  funcOne()
  console.log(3)
}

funcTwo()

console.log(4)

控制台输出:

1 2 3 4

下图为调用栈执行流程

演示01.png

每执行完一个同步任务会把该任务进行出栈。在这个例子当中每次在控制台输出一次,则进行一次出栈处理,直至全部代码执行完成。

演示二:小试牛刀

setTimeout+Promise组合拳,了解异步代码是如何进入任务队列等待执行的。

console.log(1)

setTimeout(() => {
  console.log('setTimeout', 2)
}, 0)

const promise = new Promise((resolve, reject) => {
  console.log('promise', 3)
  resolve(4)
})

setTimeout(() => {
  console.log('setTimeout', 5)
}, 10)

promise.then(res => {
  console.log('then', res)
})

console.log(6)

控制台输出:

1 promise 3 6 then 4 setTimeout 2 setTimeout 5

流程图执行-步骤一:

先执行同步代码,如遇到异步代码,则把异步回调事件放到后台监听对应的任务队列

image.png

  1. 执行console.log(1),控制台输出1

  2. 执行定时器,遇到异步代码,后台注册定时器回调事件,时间到了,把回调函数() => {console.log('setTimeout', 2)},放到宏任务队列等待。

  3. 执行创建Promise实例,并执行其中同步代码:执行console.log('promise', 3),控制台输出promise 3;执行resolve(4),此时Promise已经确定为完成fulfilled状态,把promise.then()的回调函数响应值设为4

  4. 执行定时器,遇到异步代码,后台注册定时器回调事件,时间未到,把回调函数() => { console.log('setTimeout', 5) }放到后台监听。

  5. 执行promise.then(res => { console.log('then', res) }),出栈走异步代码,把回调函数4 => { console.log('then', 4) }放入微任务队列等待。

流程图执行-步骤二:

上面已经把同步代码执行完成,并且把对应异步回调事件放到了指定任务队列,接下来开始事件循环

image.png

  1. 扫描微任务队列,执行4 => { console.log('then', 4) }回调函数,控制台输出then 4

  2. 微任务队列为空,扫描宏任务队列,执行() => {console.log('setTimeout', 2)}回调函数,控制台输出setTimeout 2

  3. 每执行完一个宏任务,需要再次扫描微任务队列是否存在可执行任务(假设此时后台定时到了,则会把() => { console.log('setTimeout', 5) }加入到了宏任务队列末尾)。

  4. 微任务队列为空,扫描宏任务队列,执行() => { console.log('setTimeout', 5) },控制台输出setTimeout 5

演示三:稍有难度

setTimeout+Promise组合拳+多层嵌套Promise

console.log(1)

setTimeout(() => {
  console.log('setTimeout', 10)
}, 0)

new Promise((resolve, reject) => {
  console.log(2)
  resolve(7)

  new Promise((resolve, reject) => {
    resolve(5)
  }).then(res => {
    console.log(res)

    new Promise((resolve, reject) => {
      resolve('嵌套第三层 Promise')
    }).then(res => {
      console.log(res)
    })
  })

  Promise.resolve(6).then(res => {
    console.log(res)
  })

}).then(res => {
  console.log(res)
})

new Promise((resolve, reject) => {
  console.log(3)

  Promise.resolve(8).then(res => {
    console.log(res)
  })

  resolve(9)
}).then(res => {
  console.log(res)
})

console.log(4)

上一个演示说明了流程图执行的详细步骤,下面就不多加赘叙了,直接看图!

talk is cheap, show me the chart

image.png

上图,调用栈同步代码执行完成,开始事件循环,先看微任务队列,发现不为空,按顺序执行微任务事件:

嵌套02.png

上图,已经把刚才排队的微任务队列全部清空了。但是在执行第一个微任务时,发现还有嵌套微任务,则把该任务放到微任务队列末尾,然后接着一起执行完所有新增任务

嵌套03.png

最后微任务清空后,接着执行宏任务。到此全部事件已执行完毕!

控制台完整输出顺序:

1 2 3 4 5 6 7 8 9 10

演示四:setTimeout伪定时

setTimeout并不是设置的定时到了就马上执行,而是把定时回调放在task queue任务队列当中进行等待,待主线程调用栈中的同步任务执行完成后空闲时才会执行。

const startTime = Date.now()
setTimeout(() => {
  const endTime = Date.now()
  console.log('setTimeout cost time', endTime - startTime)
  // setTimeout cost time 2314
}, 100)

for (let i = 0; i < 300000; i++) {
  // 模拟执行耗时同步任务
  console.log(i)
}

控制台输出:

1 2 3 ··· 300000 setTimeout cost time 2314

下图演示了其执行流程:

setTimeout假延时.png

演示五:fetch网络请求和setTimeout

获取网络数据,fetch回调函数属于微任务,优于setTimeout先执行。

setTimeout(() => {
  console.log('setTimeout', 2)
}, 510)

const startTime = Date.now()
fetch('http://localhost:3000/test').then(res => {
  const endTime = Date.now()
  console.log('fetch cost time', endTime - startTime)
  return res.json()
}).then(data => {
  console.log('data', data)
})

下图当前Call Stack执行栈执行完同步代码后,由于fetchsetTimeout都是宏任务,所以走宏任务Web API流程后注册这两个事件回调,等待定时到后了,由于定时回调是个普通的同步函数,所以放到宏任务队列;等待fetch拿到服务器响应数据后,由于fetch回调为一个Promise对象,所以放到微任务队列。

fetch.png

经过多番刷新网页测试,下图控制台打印展示了setTimeout延时为510msfetch请求响应同样是510ms的情况下,.then(data => { console.log('data', data) })先执行了,也是由于fetch基于Promise实现,所以其回调为微任务。

b475cbb38b0161d3e7f5f97b45824b31.png

五、结语

这可能只是简单的JavaScript代码执行事件循环流程,目的也是让大家更直观理解其中原理。实际执行过程可能还会读取堆内存获取引用类型数据、操作dom的方法,可能还会触发页面的重排、重绘等过程、异步文件读取和写入操作、fetch发起网络请求,与服务器建立连接获取网络数据等情况。

但是,它们异步执行的回调函数都会经过图中的这个事件循环过程,从而构成完整的浏览器事件循环。

昨天以前首页

夸克 AI 浏览器全面升级,可随时唤起千问

作者 莫崇宇
2025年11月26日 14:55

现在的浏览器,越来越「重」了。

写方案要开着 Word,查资料要切回浏览器,回消息又得跳到微信。屏幕被切得支离破碎,注意力也跟着碎掉。每次想让 AI 帮忙,都要先经历一番「寻找 AI 在哪里」的折腾。

这周,我试着把主力浏览器换成了全面升级的夸克 AI 浏览器。

体验下来,背靠阿里 Qwen 全球大模型,全面融合千问 AI 助手的夸克,发布六大千问 AI 套件,实现系统级「全局 AI」的创新产品形态,随时唤起千问,一句话帮你干活,效率拉满。

用户无需切换标签或应用,就能唤起千问读屏、快捷框、侧边栏、悬浮球、划词、截屏进行提问、协作……主打一个条条大路通千问。

它给我的第一感觉,就是在对标 Chrome 的基础上走得更远,想让 AI 成为你的「最强外脑」和「随身助理」。

体验随时桌面唤起千问方式后,让我彻底告别传统浏览器

先说最核心的,随时桌面唤起千问,这也是夸克和 OpenAI 前不久发布的浏览器 Atlas 最大的差异点。

Atlas 的理念是让 AI 成为操作系统的一部分,但它的实现方式相对保守,更多是在应用间调度。

而夸克则更激进,从底层架构彻底让 AI 融入浏览行为、以全新 AI 交互形态的出现,手撕传统浏览器,随时随地唤起千问 AI 助手,实现「边浏览边对话、边看边总结、即问即答」的丝滑体验。

举个实际场景,只需随手按下快捷键,就能让千问帮我列一个关于「社交媒体对青少年心理健康影响」的论文大纲,从而构建起连贯的学习工作流。

这种「无感接入」的设计,才是系统级 AI 浏览器的真正价值。

一个侧边栏,干掉你一半桌面操作

如果你的日常就是跟一摞摞 AI 技术论文打交道,英文不是很好,我们可以让「千问侧边栏」直接翻译、并总结核心观点。甚至让千问基于文档内容继续深挖准问,完全不用复制-粘贴-复制,也不用离开当前的标签页。

同理,阅读各种新闻资讯时,看到某辆新车发布,我们可以直接问千问侧边栏的「这款车适合哪些人使用?」。千问不仅分析当前页面,还能结合背景知识给出对比,省去了开新标签搜索的麻烦。

刷各种网页自然也是刚需,但信息量一多,自己逐条看、逐个理解既耗时间又费精力。这时候就可以果断交给千问来处理,它也能提炼重点。

千问侧边栏最妙的地方是边浏览边对话、边看边总结。传统浏览器要么让你切标签, 要么跳转新页面, 而夸克 AI 浏览器的设计让交互足够丝滑, 你几乎感觉不到在「使用 AI」。

截图即答案,划词即解释

遇到图表,截个屏,框选区域,千问直接解读数据趋势、图表含义。看到一些复杂的 AI 图片,也可以让它帮忙「整理图片中的动物职业,并翻译成中文」,千问立刻给出结构化总结。

截图提取图片文字也很顺手。

我随手截了一张三宅一生曾经客串出现在苹果「Think Different」广告宣传活动的广告语,它就能准确识别这是苹果的广告词,并给出解释。

在逛知乎时,看到一些不懂的词汇也能直接划词, 千问自会弹出解释。想追问也行,这体验四舍五入等于冲浪时随身带着一个「AI 辟谣器」。

此外,更进阶的场景是「千问读屏」功能。

这个功能的意思是,千问可以「看到」你电脑屏幕上的任何内容,它不仅能与 Microsoft Word 和 WPS 等应用深度联动,还支持快捷划词提问、截屏提问等操作。

简言之,你在用 Word 写文档、在 Excel 做表格、千问都能理解你正在做什么,并基于屏幕内容给出建议。诶,用着用着,真有种生活工作搭子的感觉。

比如我在 Word 写东西,让千问把屏幕上的《蜀道难》改写成剧本,它立马开写。这套联动还能把截屏、划词、共享的内容自动同步到侧边栏,让你能无缝追问。

让 AI 替你干活,一句话的事

除了侧边栏等方式,唤起千问的方式也灵活多样。

比如按下「Alt + 空格」(Mac:option+空格),然后在搜索框里直接说人话就行。

「帮我写一份 iPhone 18 Pro 产品发布会邀请函」「把这段翻译成英文」。这种一句话叫 AI 干活的模式,直接取代传统搜索那堆蓝色链接,从查资料到写文案、从总结到翻译,全程闭眼托管。

它甚至能一句话做表格、一句话生成 PPT,或者直接转换格式,比如把黑板照片里的字直接提取成 Word 文档。

要做到这样其实并不容易,离不开千问对浏览器场景的深度定制。

它能理解你的意图、网页结构、上下文关系, 真正做到即问即答。

除了 AI 能力,浏览体验也没落下。

夸克的标签管理做得很聪明。自动分组、按域名排序,还能识别高内存标签自动优化。实测开 30+ 标签,主打一个纵享丝滑。

还有一个让人特别惊喜的地方,就是连文档的编辑也下了不少功夫。试着直接把 PDF 拖进夸克 AI 浏览器, 直接打开、标注、编辑、转格式,无需下载。

查资料时直接批注,外文 PDF 还能直接对照翻译,省下的时间都是生产力。

从官方 Demo 来看,跨设备隔空传文件更是顺滑。文字、文档、大文件都能手机电脑无缝传输,100G 文件也不怕。网盘整理也变得智能了。一句话就能搜到文件,不用再翻来翻去。

这些听起来都是细节,但加起来就是巨大体验差异。

对齐 Chrome 的性能标准后,夸克给你的是更极简的界面、零广告干扰,以及更轻的内存占用。当你习惯了这种清爽, 再打开某些国内浏览器, 满屏的推荐信息会让你怀疑人生。

哪怕是放在国内一众老牌浏览器里,这种克制也是值得表扬的。而当 AI 成为浏览器的底层能力,配合这些生产力工具,你会发现自己的工作流程被彻底重构了。

标签页的尽头,是 AI 的起点

用了几天夸克之后,我开始思考一个问题:当 AI 真正融入浏览器,它到底在改变什么?

答案可能比想象中更深刻。

过去 20 年里,浏览器的形态几乎没变。1995 年,比尔·盖茨在一份备忘录里写道:微软将操作系统作为人机接口,从而控制整个微机行业,而网景则控制了人们通向互联网的入口。

谁控制了入口,谁就能定义规则、分配流量、获得数据。2010 年,Chrome 成为稳定支持三个平台且拥有书签同步功能的浏览器。但即便如此,它的交互本质仍然是 1995 年的逻辑:

用户负责提出问题,浏览器提供零零碎碎的回答。

你依然要在多个标签页之间来回切换,手动拼凑信息,自己综合结论。这个模式延续了近 30 年,直到 AI 出现。夸克这次做的事情,本质上是在挑战这套交互范式。

通过千问读屏、悬浮球、快捷键,千问已经跨出了浏览器窗口的边界。

你在 Word 里写文章,在 Figma 里调布局,在任何一个应用场景里,千问都能理解你正在做什么,并给出针对性反馈。AI 不再局限于某个应用内部,成为整个系统的基础能力。

 

为什么是夸克先做出来?有几个原因。

首先,这与夸克的用户基础和战略定位密切相关。

随着阿里千问与夸克 AI 浏览器深度融合,也正式成为超 1 亿电脑用户的桌面级智能助理,这个量级的用户基础,意味着夸克有足够的场景数据和反馈来打磨产品体验。

浏览器作为电脑上最核心的入口,几乎涵盖用户获取信息与执行任务的所有场景。而 AI 在这个场景下的能力空间巨大,也标志着阿里巴巴千问正加速实现对 C 端场景的全面覆盖——从移动端到桌面端,从对话框到操作系统级的全局调用。

此外,浏览器的智能化上限,取决于底座模型的智商。

夸克直接接入了阿里 Qwen 全球领先大模型,这意味着它处理中文长文本和复杂逻辑推理时,天然就有语言理解上的优势。毕竟,模型能否准确理解语境,直接决定了体验的质量。

一键唤起是普通人的刚需,而做到全局唤起千问、千问读屏这类功能,需要深入操作系统底层,依赖于阿里的资源支持,夸克才敢于在这个方向上持续投入,而不是浅尝辄止。

与此同时,在隐私敏感和追求效率的当下,夸克选择了一条「反直觉」的路径——无广告、极简界面、内存优化。对于大多数普通用户,他们不需要 100 个插件,他们只需要一个能搞定 90% 麻烦事的 AI 按钮。

当然,坦诚地说,夸克面临的挑战也很明显。

Chrome 强大的插件生态和用户长期的使用惯性,是任何挑战者都难以短期撼动的壁垒。对于重度依赖特定插件的极客用户,迁移成本依然存在。用户对国内浏览器「大杂烩」的刻板印象,也需要夸克持续的克制来打破。

但无论如何,AI 浏览器显然是个趋势。

Chrome、Perplexity、OpenAI 都在做类似的事,国内浏览器都在跟进。

夸克这次的战略升级,选择主动出击抢占身位。依托通义千问 Qwen 在全球大模型评测中的强劲表现,夸克把浏览器从「浏览网页的辅助工具」变成「系统级的任务助手」,率先给出了 AI 浏览器未来形态该有的样子,也有利于它在这一赛道的早期阶段建立心智优势,并在下一轮竞争中获得更大的主动权。

而这个逻辑一旦成立,接下来的演化就清晰了。

你看论文时可以随时唤起 AI 解释概念。写代码时可以随时让 AI 审查逻辑。做设计时可以随时请 AI 给出建议。所有这些场景的共同点是:AI 理解你正在做什么,知道你需要什么,在你需要的时候出现。

如果说 Chrome 教会了我们什么是好浏览器,夸克则给 AI 浏览器打了个样板,放眼全球,都是领先的产品形态。未来的操作系统,将是以「任务」为中心。当你遇到问题,本能反应不是打开搜索引擎搜一下,而是唤起 AI 问一下。

这条路难走,因为它需要更深的技术积累、更克制的产品设计、更长期的用户教育。 但一旦走通,壁垒也会更高。

毕竟,习惯才是最难被撼动的力量。

#欢迎关注爱范儿官方微信公众号:爱范儿(微信号:ifanr),更多精彩内容第一时间为您奉上。

爱范儿 | 原文链接 · 查看评论 · 新浪微博


浏览器渲染原理

作者 z_nb_z_k
2025年11月25日 17:04

在日常面试中,我们经常会遇到这样一个问题: “在浏览器输入 URL 后,页面是如何展现出来的?”
这个看似简单的问题,其实背后涉及浏览器渲染、网络请求、解析执行等一系列复杂流程。本文将聚焦于 浏览器渲染原理,带你梳完整过程(本文主要讲解渲染进程的工作,网络进程这里不详细解释)。


整个过程可以分为两个关键步骤:

  1. 浏览器解析 URL 并发起请求 —— 浏览器首先会解析你在地址栏输入的 URL,经过 DNS 查询、TCP/TLS 连接建立等步骤,将请求发送到服务器。
  2. 解析和渲染返回结果 —— 浏览器收到服务器返回的 HTML、CSS、JS 等资源后,会依次解析、构建 DOM、CSSOM等,最终通过渲染引擎将页面呈现出来。

浏览器解析 URL

参考资料:Chrome 官方博客:导航过程中会发生什么

第 1 步:处理输入

当用户在地址栏输入内容时,浏览器进程中的 界面线程 会立即启动,判断用户输入的是搜索关键词还是网址,并据此决定:要么将请求定向到搜索引擎,要么直接访问指定网站。

第 2 步:开始导航

用户按下 Enter 键后,UI 线程会发起网络请求以获取网站内容。此时,标签页角落会显示加载旋转图标,提示页面正在加载。

网络线程会按照网络协议处理请求,包括 DNS 查询 和建立 TLS 连接(如果是 HTTPS)。

若服务器返回 HTTP 301 等重定向响应,网络线程会通知 UI 线程,并根据重定向地址发起新的请求,完成导航流程。

第 3 步:读取响应

网络线程开始接收服务器返回的响应数据(载荷),并查看前几个字节以判断内容类型。虽然响应头中的 Content-Type 应指明数据类型,但由于可能缺失或错误,浏览器会执行 MIME 类型嗅探

  • 如果响应是 HTML,数据会传递给渲染进程进行渲染。
  • 如果是 ZIP 或其他文件,则交由下载管理器处理。

同时,浏览器会进行安全检查:

第 4 步:查找渲染进程

完成安全检查后,如果网络线程确认导航可继续,它会通知界面线程,界面线程随后查找或启动合适的 渲染进程,以准备渲染网页。

优化机制:由于网络请求可能需要数百毫秒,浏览器会在第 2 步发起请求时,就尝试并行启动渲染进程。这意味着当数据到达时,渲染进程通常已处于待机状态。


解析和渲染返回结果

当数据和渲染进程都准备就绪后,浏览器进程会向渲染进程发送一次 提交导航(Commit Navigation) 的 IPC 信号,并将来自服务器的 HTML 数据流交给渲染进程。
渲染进程在接收到该指令后,会将“解析 HTML”的工作加入自身 渲染主线程(Main Thread) 的消息队列。

在事件循环机制的调度下,渲染主线程从队列中取出该任务并开始执行,正式进入渲染流程。

整体的渲染步骤如下图所示:

image.png


第 1 步:解析 HTML 与构建 DOM / CSSOM

服务器返回的 HTML 本质上是 字节流,浏览器会将其解码为 字符串,再进一步解析成可操作的数据结构——这就是我们熟悉的 DOM 树。同样,CSS 也会被解析成对应的 CSSOM 树

在解析过程中:

  • 遇到 HTML 标签 ⇒ 主线程将其转换为 DOM 节点并加入 DOM 树中(树的根节点是 document)。
  • 遇到 CSS ⇒ 根据层叠规则解析生成 CSSOM 树(根节点为 StyleSheetList)。

在浏览器解析 HTML 时,即使遇到 <link><style> 标签,主线程也会继续向下解析 HTML,而不会被阻塞。这是因为浏览器会启动一个 预解析线程(Preload Scanner),提前发现并下载 CSS 等外部资源。 CSS 的下载和准备工作在这个预解析线程中完成,不会占用主线程,所以CSS 不会阻塞 HTML 解析。只有当 HTML 构建完成、CSS 下载完毕后,浏览器才会利用 CSS 生成 CSSOM 树,与 DOM 树结合形成 渲染树,用于后续页面绘制。 通过这种方式,浏览器实现了 DOM 构建与 CSS 下载的并行,提高了解析效率,同时保证页面渲染的正确性。

当浏览器解析到 <script> 标签时,会暂停 DOM 的构建,等待外部脚本下载完成,并在主线程中完成解析与执行。
原因在于:JavaScript 有能力通过 document.write、DOM API 等方式直接修改正在构建的 DOM 结构
如果不暂停解析,一边构建 DOM、一边执行 JS,就可能导致 DOM 状态出现不一致。 因此,为了保证 DOM 构建过程的正确性和可预测性,浏览器必须中断 HTML 解析,优先执行脚本。这就是 JavaScript 会阻塞 DOM 构建的根本原因

在解析结束之后,会得到:DOM 树CSSOM 树


第 2 步:样式计算

主线程会遍历构建完成的 DOM 树,并为树中每一个节点计算出它的最终样式,这个过程称为 样式计算(Computed Style)
样式计算结束后,我们获得的是一棵“附带最终样式信息的 DOM 树”,为后续布局阶段提供基础。

关于样式计算的具体细节,可以参考我另一篇文章:样式计算


第 3 步:布局

布局(Layout)是浏览器确定页面中每个元素几何位置和大小的过程。主线程会遍历 DOM 树,结合计算后的样式信息,生成包含 坐标、边界框尺寸 等几何信息的 布局树(Layout Tree)

布局树与 DOM 树的结构类似,但只包含 实际在页面中显示的内容

  • 应用 display: none 的元素不会出现在布局树中,因为它们没有几何信息。
  • 应用 visibility: hidden 的元素仍然会在布局树中保留位置和尺寸信息。
  • 伪元素(如 p::before { content: "Hi!"; })虽然不在 DOM 树中,但具有几何信息,因此会出现在布局树中。
  • 其他情况如匿名块盒、匿名行盒等,也会导致布局树与 DOM 树不完全一一对应。

因此,大多数情况下,DOM 树和布局树并非严格对应,布局树只关注用于渲染的几何信息,而非完整的 DOM 结构。


第 4 步:分层

为了确定哪些元素需要位于哪些层,浏览器主线程会遍历 布局树 并生成 层树(Layer Tree) 。在 Chrome DevTools 的性能面板中,这一阶段通常显示为“更新层树”。

分层的核心目的是 提升渲染效率。浏览器会根据提示提前为元素分配独立层,优化动画或滚动等操作的渲染效率。
将页面划分为独立的层后,如果某个层的内容发生变化,浏览器只需要重绘该层,而无需重新渲染整个页面。

如下图所示:
image.png

某些 CSS 属性和布局特性会自动触发分层,例如:

  • transformopacityfilter
  • position: fixed / sticky
  • overflow: scroll / auto
  • z-index / 堆叠上下文

此外,如果希望浏览器为某些元素创建独立层,可以使用 will-change 属性向浏览器发出提示:

.menu {
  will-change: transform;
}
补充说明
  • 分层不会改变 DOM 结构或布局,只是为渲染过程划分优化单位。
  • 虽然分层可以提高性能,但过度分层会增加 GPU 内存占用,因此应谨慎使用 will-change,仅对频繁变化的元素设置。

第 5 步:绘制(生成绘制指令)& 分块

  • 绘制阶段(主线程)

    • 主线程会为每个独立层生成对应的 绘制指令集(Paint Commands),用于描述这一层应该如何渲染,包括颜色、边框、文字、图片等。
    • 绘制指令通常以矢量或命令序列的形式存在,而不是直接生成位图,这样可以提高渲染效率和重绘灵活性。
    • 绘制完成后,主线程将每个层的绘制信息 提交给合成线程(Compositor Thread),主线程任务结束。
  • 分块阶段(合成线程)

    • 合成线程为了优化 GPU 渲染,会将每个图层划分为更小的 绘制块(Tiles)

    • 分块的优势:

      • 局部更新:只重绘变化的块,减少不必要的 GPU 负担。
      • 并行处理:可从线程池中获取多个线程同时处理不同块,提高处理速度。
      • 内存优化:分块渲染可以让 GPU 更好地管理显存,避免一次性加载大图层导致内存峰值过高。

image.png


第 6 步:光栅化

在渲染流程中,合成线程会将页面的图层信息(Layer / Tile)交给 GPU 进程,以高效完成 光栅化

光栅化的本质是将矢量图形、文本、图层等内容转化为 像素位图(bitmap) ,方便最终在屏幕上显示。

  • GPU 进程通常会开启多个线程并行处理不同的瓦片(Tile),提升渲染效率。
  • 浏览器会优先处理靠近视口(Viewport)区域的块,以确保用户能尽快看到可视内容,提高 首屏渲染性能(FCP / First Contentful Paint)
  • 光栅化完成后,每个图层或瓦片都会生成一块位图,为最终的 合成(Compositing) 做准备。

第 7 步:页面绘制

在光栅化完成后,合成线程(Compositor Thread) 会拿到每个图层(Layer)和瓦片(Tile)的位图,并生成对应的 绘制指引(quad)

  • Quad 的作用:指示每块位图在屏幕上的显示位置,同时包含旋转、缩放、透明度等变换信息。
  • 高效变换:变换(transform)操作发生在合成线程,而不需要经过渲染主线程。这就是 CSS transformopacity 能够实现 GPU 加速、渲染性能高的原因。

合成线程生成 quad 后,会将其提交给 GPU 进程。GPU 进程通过系统调用与 GPU 硬件交互,完成最终的像素合成,将页面内容呈现在屏幕上。

补充总结:

  • 渲染主线程负责构建 DOM、CSSOM、渲染树、布局和绘制图层内容;
  • 合成线程只负责图层组合和变换操作;
  • GPU 负责将最终像素输出到显示器,实现高效渲染。

流程总结:

第一步 浏览器解析 URL:

image.png

第二步:解析和渲染:

image.png

❌
❌