页面是如何渲染的?通常会得到“解析 HTML、css 合成 Render Tree,就可以渲染了”的回答。但是具体都做了些什么,却很少有人细说,我们今天就从 Chrome 的性能工具开始,具体看看一个页面是如何进行渲染的,以及进行页面优化时需要关注哪些指标。

以“老二次元”网站 bilibili 为例,我们将通过分析 performance 面板,串联起 Chrome 页面渲染流程,以及页面的部分量化指标的含义,来看页面具体是如何渲染的。

获取performance数据

首先,打开Chrome devTools, 选择 performace面板,点击录制按钮开始录制。

之后为了防止我们分析页面时出现无关的干扰,我们通过以下步骤降低干扰项:

1、打开 Chrome 无痕模式。

2、关闭所有在 Chrome 无痕模式下启用的拓展(如果有的话)。

3、在地址栏输入 www.bilibili.com 前,先打开 devTools,选择 performance 面板,点击录制按钮。

4、在已经录制的情况下,地址栏回车,请求 B 站,大概 10s 后,停止录制。

我们从上到下,将图分成以下几块,如下图所示:

1、控制面板

2、概览面板

3、网络面板

4、Web Vitals

5、线程面板

6、内存面板

7、聚合面板

控制面板

控制面板有 4 部分内容,分别为:

  • disable javascript samples:启用后会隐藏一些 JS 调用栈的展示。在一些性能较弱的设备例如移动端上,可以开启这项功能。

  • Network:可以用来模拟各种网络状况。

  • enableadvanced paint instrumention (slow):启用后 paint 面板会显示与绘制相关事件的更详细的信息。

    CPU:可以用来模拟不同的 CPU 性能。

概览面板

概览面板是各项指标的一个概览,包含了 FPS 帧数、CPU 占用、NET 情况、内存使用情况等。

简单举个例子,比如 FPS 帧数可以直观的看出 FPS 的高低,绿色代表低的部分。而 CPU 栏的黄色代表着 js,紫色代表计算样式和布局,绿色代表绘制。

网络面板

网络面板用于展示正在请求中的各部分的组成情况。

Web vitals

Web vitals 是网站的 Web 体验指标,其中包括 LCP(最大内容绘制)、FID(首次输入延迟)、cls (累计布局偏移)等。

线程面板

线程面板用于展示渲染当前页面所使用到的线程,包含有 Main 线程、GPU 线程、Raster 线程、Chrome_ChildIOThread、Compositor 线程等等。其中 Main 线程,就是我们平时说的大部分 js 的运行环境,即主线程。

内存面板

展示 js 内存、GPU 内存、节点数、监听事件数的变化。

聚合面板

当点击主线程中的火焰图时,此面板会显示显示具体包含执行时间、执行组成、调用栈等等的信息集合。

Chrome是如何渲染页面的?

第一个请求

以第一个请求为例,我们来具体看一下 Chrome 是如何进行页面渲染的?依然是以对 https://www.bilibili.com 的请求为例,来看一下 1ms 的 performance 面板,即下图中红线部分、中间 NET 栏蓝色细长条开始的部分和 Network 中水平箱线图开始的部分。

其中两边横线中间深浅色方框的部分是水平箱线图,是用来展示某部分在整体中的比例关系。比如我们看到这个长长的箱型图,通过直观感受,就能知道对前面一部分横线挺长的,蓝色部分里浅色部分很长,深色的短,右边的横线几乎看不到。那这些又分别能展示什么信息?

首先,点开箱型图最下方的聚合面板(Summary),上面赫然写着:此乃页面源。欲求小破站, 终生皆让我……耗时一秒半。

然后在 Network tab 里查看该请求的 timing 部分,可以得到如下图:

这里的各个部分分别代表:

  • Queueing(排队):浏览器会在一些情况下让请求排队等待,比如这个请求的优先级不高,有更高优先级的请求存在;在使用 HTTP/1.0 或者 HTTP/1.1 时,同域请求最大并发数量为 6 个,此时已经达到了最大值;而上图中的请求是属于最高优先级的第一个请求,即浏览器正在硬盘缓存中分配空间,从图上可以看到有 14.72ms 用于在磁盘缓存中分配空间。

Stalled(停顿):它可能会因为上述排队中的任何原因而停顿。

  • DNS lookup(DNS 查询):解析这个域名的IP地址。需要注意的是,当我们多次访问同一域名时,这部分不会出现在 timing 中。

  • Initial connection(初始连接):浏览器建立连接,包括 tcp 三次握手、重试以及协商 SSL。图中的紫色部分,就代表了在初始连接过程中的 SSL 协商部分。

  • Request sent(发送请求):正在发送请求

  • Waiting (TTFB) 等待第一字节时间:浏览器在等待第一个响应的字节,TTFB 即 Time To First Byte。这个时间包括一个往返的延迟和服务准备响应的时间之和。

  • Content Download (内容下载):浏览器正在接收响应,浏览器可以通过网络或者 serviceWorker 来直接接收。这个值是读取响应体的总时间。由于网络不佳或者浏览器正在忙于执行其他工作而延迟了对响应体的读取,读取的时间可能会比预期的要长。

这里相信已经有小伙伴注意到了,当浏览器忙于其他事情时也会让读取时间变长。也就是说,当你的 js 把主线程长期占据的时候,就会影响 content download。

下图是 Network 下的对应资源的 waterfall:

现在我们回到最开始说的各色横条上,在水平箱线图中左上角的深蓝色小方块代表着这个请求有着更高的优先级。遇到有浅蓝色的,则表示较低优先级。同时左边横线对应 Network 面板中显示的 Request Sent之前的所有事情的时间。浅色的 bar对应 Network 中 Request Sent 和 Waiting(TTFB)的时间。深色的 bar对应 Network中Content Download 的时间。右边的横线表示等待主线程所花费的时间,在 Network 面板中没有体现。

此外,可能还有些同学注意到,在蓝色箱线图上面还可以看到还有几个灰色的箱线图。不是说www.bilibili.com 是页面的第一个请求吗,难道它之前还有请求?

事实上,这个灰色箱线图相当于上一个页面的结束。如果我们是通过重新录制的方式记录 performance,那就会经历页面刷新的过程。而这几个灰色的其实就是页面刷新 unload 时发起的,是 bilibili 用来记录页面卸载时的一些数据。

说回到箱线图,可以看到在 summary 中显示 Duration 1.08 s (822.88 ms Network transfer + 260.20 ms resource loading)。这个的意思是 260ms 的时间是在 resource loading ,这里resource loading所花费的时间其实就是箱线图右侧的那条横线,等待主线程的时间。

而在 main 进程中,有横线结束的地方,可以看到解码的数据 138,933 Bytes。

这里就出现了几个问题:为什么Encode Data 33479 bytes 算下来是 33479/1024 = 32.69 k,而不是前面 Network 面板里的 33.5k ? 而且 Decode body 138993/1024 = 135.7k 也不是前面的 139k?缺少的一部分数据是什么呢?

为了验证这个问题,需要清空过去所有请求记录,重新点击录制,录制完成后,导出网络请求的 HAR 文件。使用 vscdoe 打开 json 格式的 HAR 文件,寻找 GET https://www.bilibili.com/ content-Type: text/HTML 的那个请求。经过前后的文件对比,找到了这个请求的 response content:

可以看到,图上的 size 有140682 字节。text则是 base64 编码的 HTML 内容,已经被 decode 过。需要注意的是,这里的 decode 不是对 base64 的 decode,是对 gzip 的 decode。

而在这个 text 内容之后,还有一段如下内容:

其中的 _transferSize: 35593 是网络传输的体积,即传输的体积 35593 和 decode 体积 140682。同时我们在 performance 里的主进程中的 finish loading中可以看到下图数据:

这样一看,二者是相同的。说明这个 HTML 的传输体积就是 35593 Bytes。

那为什么在 Network 面板里,我们看到的是 35.6k transferred over Network 呢?

这是因为在 Network 里展示的体积,不是除以 1024 计算的,而是除以 1000,然后四舍五入后的结果。

不过 Summary 里的 pending for xxx ms,似乎是也是等待主线程的时间,但它又是如何在 performance 体现的。目前,我还没搞清楚,如果有了解的小伙伴欢迎留言讨论~

请求其它资源

言归正传,我们现在获取到了 bilibili 网站的 HTML,接下来就需要对这个 HTML 进行处理。

通过 response header 得到 content-type:html,此时会创建一个个渲染进程,也就是主线程的这个进程。但是可以看到在主线程中的蓝色 parse HTML 之前,已经有很多 set request 被发起了,而且这些 send request 都是 HTML 文档中的一些 js 和 css。

为什么会这样呢?不应该是先解析 HTML,才能知道对哪些资源进行发起请求吗?

在 HTML 中引入的 js,存在修改 Dom 的可能,所以浏览器一般在遇到 script 标签后,会先暂停 HTML 解析,优先 js 的下载和执行。但是下载是相对耗时的,如果因为下载时间久而卡住了页面解析,很容易导致用户体验变差,因此 Chrome 采用了一些优化策略。

具体来说,就是当 Chrome 渲染引擎接收到 HTML 的字节流时候,会开启一个专门用来分析字节流中所包含 js、css 文件的预解析线程。解析到相关信息之后,预解析线程会提前开始下载这些资源文件,这样在需要使用的时候就可以直接执行,避免了下载的等待时间。

但是也能观察到,在Parse HTML蓝色方块下方,还有一些 send request,这些怎么就不是提前下载的呢?

我的理解是,这些资源其实都是在预解析线程下载的,尽管在时间上会存在重叠,但和主线程不属于同一个线程,所以 performance 工具会这么显示。但这又带来了另一个问题,为什么有些 js 明明在 HTML 的后面,却在前面就 send request 了,而有些 link/script 明明写在 HTML 里的前面,却在 performance 里后 send request?

这是跟资源的优先级有关。比如普通的 script 标签引用的资源,普通 link 引用的资源,或是rel=prelaod 或 as="style"预加载的资源,可能会被优先处理。而当资源是 prefetch,或者用 这种方式的,由于优先级低,就会被延后下载。一般的其他资源,则按顺序下载。

回到 Network,可以看到在 www.bilibil.com 的箱线图之后,是一连串 js、css、Webp 资源需要加载的请求被发起了。把鼠标移动到这些箱线图上,会看到上面有优先级 lowest low high highest,这就表示了资源的重要程度。

那么这些资源的优先级是如何评定的?一般来说,访问域名获取的 HTML、 以及预加载资源时as="style",拥有最高优先级。普通的

从 B 站出发,用 Chrome devTools performance 分析页面如何渲染的更多相关文章

  1. Chrome DevTools & performance optimization

    Chrome DevTools & performance ptimization https://www.bing.com https://developers.google.com/web ...

  2. Chrome DevTools & performance & keywords

    Chrome DevTools & performance & keywords performance / 优化性能 https://developers.google.com/we ...

  3. 黄聪:如何扩展Chrome DevTools来获取页面请求

    1. Chrome DevTools Extension 熟悉React的同学,可能对React Developer Tools并不陌生,     刚看到的时候,我也觉得很神奇, 因为React De ...

  4. 使用Chrome工具来分析页面的绘制状态

    Chrome Canary(Chrome “金丝雀版本”)目前已经支持Continuous painting mode,用于分析页面性能.这篇文章将会介绍怎么才能页面在绘制过程中找到问题和怎么利用这个 ...

  5. [Forward]Improving Web App Performance With the Chrome DevTools Timeline and Profiles

    Improving Web App Performance With the Chrome DevTools Timeline and Profiles We all want to create h ...

  6. chrome devtools的debug相关

    搜索ctrl+p:搜索Sources面板中指定的文件:然后在主窗口文件标签右键选择reveal in navigator可以在目录中显示当前文件.ctrl+f:搜索devtool主显示窗口所在文件的指 ...

  7. Chrome DevTools 调研笔记

    1  说明 此篇文章针对Chrome DevTools常用功能进行调研分析.描述了每个功能点能实现的功能.应用场景和详细操作. 2  Elements 2.1  功能 检查和实时更新页面的HTML与C ...

  8. 【转】chrome devtools protocol——Web 性能自动化

    前言 在测试Web页面加载时间时,可能会是这样的: 打开chrome浏览器. 按F12打开开发者工具. 在浏览器上打开要测试的页面 查看开发者工具中Network面板的页面性能数据并记录 或者在开发者 ...

  9. 全新Chrome Devtool Performance使用指南

    运行时性能表现(runtime performance)指的是当你的页面在浏览器运行时的性能表现,而不是在下载页面的时候的表现.这篇指南将会告诉你怎么用Chrome DevToos Performan ...

  10. chrome devtools tip(2)--自定义代码片段,构建你的工具箱

    平常开发中,有些代码片段常常用到的,比如,获取 url 参数,rgb转16进制,打印下当前页面的性能数据,给所有的 span 加个样式, 防抖节流,fetch接口,类似 jquery这样的顺手 选择 ...

随机推荐

  1. 使用pycharm or vscode来编写python代码?

    pycharm社区版可用于商业项目 pycharm社区版可用于商业项目,来源于官方的回答:Can I use Community Editions of JetBrains IDEs for deve ...

  2. 关于解决pip安装python第三方库超时的问题

    直接换源下载 1. 设置超时时间,安装txt 文件内安装包 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --default-time ...

  3. Qt开发:Windows 下进程间通信的可行桥梁:窗体消息SendMessage

    Qt开发:Windows 下进程间通信的可行桥梁:窗体消息 注:窗体消息仅适用于有窗口的进程,如果没有窗口是无法收到窗体消息的(哪怕是隐形的都可以),比如Qt中如果需要使用WindowsMessage ...

  4. Django框架:10、Ajax补充说明、多对多三种创建方法、Django内置序列化组件、批量操作数据方法、分页器思路、form组件

    Django框架 目录 Django框架 一.Ajax补充说明 1.针对前端回调函数接受值的说明 二.多对多三种创建方式 1.自动创建 2.纯手动创建 3.半自动创建 三.Django内置序列化组件 ...

  5. Windows缓冲区溢出实验

    Windows缓冲区溢出 前言 windows缓冲区溢出学习笔记,大佬勿喷 缓冲区溢出 当缓冲区边界限制不严格时,由于变量传入畸形数据或程序运行错误,导致缓冲区被"撑暴",从而覆盖 ...

  6. Less-1(GET字符型)

    union联合注入(方法一) 进入靶场 按照要求提交一个id:http://192.168.121.131/sqli/Less-1/?id=1 数据库执行语句:select * from news w ...

  7. [seaborn] seaborn学习笔记7-常用参数调整Adjustment of Common Parameters

    7 常用参数调整Adjustment of Common Parameters(代码下载) 主要讲述关于seaborn通用参数设置方法,该章节主要内容有: 主题设置 themes adjustment ...

  8. python进阶之路8 字典、元组、集合内置方法 编码理论

    内容回顾 作业讲解 1.前期不熟练的情况下一定要先写注释 2.一定要仔细思考每一行代码的含义 3.自己不会的代码或者不熟练的代码一定要多敲多练 数据类型内置方法简介 所有的数据类型基本上都自带了一些操 ...

  9. 01-Tcl基本知识

    1 Tcl基本知识 1.1 Tcl是什么? Tcl全称是Tool Command Language,是一种基于字符串的命令语言. Tcl是一种解释性语言,类似于其他脚本语言一样,直接对每条语句顺次解释 ...

  10. Redis--回顾提要

    一.写在前 知识学了就忘!不用就忘!我太健忘!特此记录!用于复习打卡!Redis干就完事了! 二.来辣! Redis做异步队列:一般list结构做队列,rpush生产消息,lpop消费消息,当lpop ...