共计 10203 个字符,预计需要花费 26 分钟才能阅读完成。
页面是如何渲染的?通常会失去“解析 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,或者用 <link rel=”stylesheet” href=”//s1.hdslb.com/bfs/static/jinkela/long/font/regular.css” media=”print” onload=”this.media=’all'”/> 这种形式的,因为优先级低,就会被延后下载。个别的其余资源,则按程序下载。
回到 Network,能够看到在 www.bilibil.com 的箱线图之后,是一连串 js、css、Webp 资源须要加载的申请被发动了。把鼠标挪动到这些箱线图上,会看到下面有优先级 lowest low high highest,这就示意了资源的重要水平。
那么这些资源的优先级是如何评定的?一般来说,拜访域名获取的 HTML、以及预加载资源时 as=”style”,领有最高优先级。一般的 <script>、<link> 标签、应用 preload 的预加载,领有 高优先级。应用了 async/defer 的 <script>、as=”script” 的预加载资源领有低优先级。应用了
<link rel=”stylesheet” href=”//s1.hdslb.com/bfs/static/jinkela/long/font/medium.css” media=”print” onload=”this.media=’all'”>
这种形式的,和不加 as=”xxx” 的 prefetch 预加载,就相当于异步加载,领有最低优先级。
HTML Parse
好,到当初为止,咱们曾经将用到的 js、css、图片等资源下载了,而后就该进入解析 HTML 的过程了。
在 Chrome 渲染引擎外部,有个 HTMLParser 的模块。HTML 解析器负责将 HTML 转化为 Dom 构造。HTML 解析器并不是等整个文档全副获取之后才开始解析,而是加载了 ” 足够 ” 的数据后,就开始解析了。
在 HTML Parser 的 summary 面板里能够看到,有个 Range:www.bilibili.com[0…45]。点进去看一下能够发现,定位到了 HTML 的 45 行。
这也从侧面印证了解析 HTML 的过程并不是一次全副执行完的。
HTML 的解析生成 Dom 树的过程,能够参考文章(https://medium.com/nybles/int…)。简略来说就是将字节流转换成 token,而后把 token 解析成 Dom 节点并增加到 Dom 树中。
在 HTML 解析器工作过程中,会遇到 js、css 须要解决,比方蓝色条上面有黄色的 js 执行,有 parse stylesheet 的 css(这里的两个是 vendor.css 和 index.css)解析和 cssom 的构建。
当拿到了 vendor.css 和 index.css 这两个内部款式文件之后,就开始了 Recalculate Style 的过程,也就是在进行一些可能包含递归(比方想晓得父容器的大小就得先晓得子元素的大小)的款式计算。留神,这时候 HTML 还是没有齐全解析完的,然而一旦款式计算完结,就开始 Layout 过程。
这里的 Layout 对应的是将 Dom tree 和 cssom 联合成 render tree 的过程。render tree 是不蕴含例如 <meta>、display: none 这些无需展现的元素。
分层
在款式计算之后,还须要经验一个 pre-paint 的过程,而后能力 paint。
以前这里叫做 update layer tree,2022 年 3 月份之后改成了 pre-paint。这里其实是遍历 render tree 生成 layer tree 的过程。
render tree 和 layer tree 有啥不同呢?
render tree 是 Dom 和 cssom 联合的产物,是将计算后的款式增加到了 Dom 节点上。然而目前只是晓得了节点是否可见以及可见款式,还不晓得节点的准确地位和大小,这时候就须要布局。渲染引擎从 render tree 的根节点开始遍历,通过肯定的规定解决后,将会失去一个 layout tree,这个 layout tree 准确的形容了每个视口内元素的地位和确切尺寸,所有的绝对地位都会转变成屏幕上的相对地位,在得悉了节点是否可见、款式、地位几何信息之后,渲染引擎才有机会将 render tree 上的每个节点都转换成屏幕上的像素,这个过程也就是个别说的 绘制 paint 或者栅格化 Rastering。
那 layer tree 在哪儿呢?layer tree 就在栅格化的过程当中。
在说栅格化之前,有必要提一下 Chrome 是如何将渲染视口内的内容的。
过来 Chrome 是只在用户可视区域内进行栅格化,随着用户滚动一直滚动页面而调整栅格化区域,持续栅格化并将内容填充到缺失局部。这样的毛病是当用户疾速滚动的时候,页面会有卡顿感。
而当初 Chrome 采纳了一种合成 composting 的形式,将页面中的某些局部分成不同的层,别离栅格化它们,而后在合成器线程中合成。这样在页面滚动时,原材料曾经有了(筹备好的那些层),只须要将视口内的蹭合成为一个新帧即可。这样在用户滚动时,新帧的合成效率更高。
既然须要分层,那就要晓得那些元素应该在哪一层里,所以渲染引擎须要依照肯定规定再遍历一次 layout tree 来创立 layer tree,这个过程也就是 pre-paint,以前叫做 update layer tree。
分层也须要依照肯定的规定,不是任意一个元素都能够被拎进去当做一层,次要是两个条件:
- 领有层叠上下文属性的元素会被创立成图层
页面是个二维的,然而层叠上下文属性会让 HTML 元素具备三维的概念。这些元素依照本身的属性优先级散布在垂直页面的 Z 轴之上,哪些元素领有具体参考 MDN。
- 须要被裁剪的中央会被创立为图层
当你理论的内容比容器还大的时候,就会呈现裁剪,引擎会裁剪一部分内容显示在容器区域。一般来说,呈现滚动条就会被创立为图层。
满足以上任意一个条件就会被晋升成独自一层。
那这在 Chrome devtools 哪里能够体现呢?在 devtools -> 右侧三个点 -> more tools -> layers 里能够看到页面实际上被分成了许多层。
点击左侧的具体图层,能够看到具体的绘制过程。Details 里还有被晋升为一层的起因 composition reason。
Paint
通过后面分层,咱们得悉了元素的层级关系,然而还不晓得同一层内元素的层级关系。一般来说,前面的内容会笼罩后面内容,然而浏览器该如何晓得谁该笼罩谁呢?
这就须要渲染引擎为每一个图层创立绘制记录 patint record 并确定谁先画谁后画,那么后画的必定就会笼罩先画的。绘制记录能够看做一个单向链表 div -> div -> p -> span,遍历链表即可取得绘制程序。
当初有了图层,也有了绘制记录程序,这些信息将会被提交到合成器线程中进行绘图和合成。因为一个图层可能会十分大,超过了视口面积,那么图层就会经验一次宰割过程,宰割成一个个小的图块 Tile,通常是 256256 或 512512 大小,这些图块进行会传递给栅格化线程池。池中的栅格化线程执行栅格化工作 Raster Task,将图块生成位图 bitmap,并优先生成视口左近的位图。
这个过程在 performamce 里叫做 Rasterize Paint。
栅格化过程也会应用 GPU 来减速,个别又称为疾速栅格化,GPU 栅格化。这也是为什么会有些 css 里写 will-change:transfrom 或者 transform: translateZ(0),就是为了 GPU 参加绘制。实质上是利用 will-change 和 translateZ(0) 创立了新的渲染层,从而不影响其余层级的绘制内容。
当所有的 Tile 栅格化结束,合成器线程收集 Draw Quads 的图块信息。Draw Quads 记录了图块在内存中的地位和在页面那个地位进行绘制。而后主线程收集这些 Draw quards 信息并合成合成器帧,并交给 GPU 渲染,而后才是像素呈现在屏幕之上。这个过程在 perfomrance 里是 Compositie layers。
惋惜我没有在 performance 里找到更具体的信息来展现这个过程。
页面渲染大略就是上述的过程,次要是联合 performance 面板串联起过来的那些常识。理解了页面渲染流程,咱们该如何优化页面性能呢?又须要关注那些指标呢?
页面优化关注哪些指标
这个指标不是凭空发明,也不是仅凭感觉,这应该是一些明确的、能够量化的指标。Chrome devtool lightHouse 列举了 6 个指标。
FCP
First content paint 代表浏览器渲染出第一个 Dom content 的工夫。这里的 Dom content 包含图片、非空白 canvas、svgs 等。如果你的页面里有 iframe,iframe 里的任何货色都不会被当成 Dom content。
FCP 好坏规范也是随着收集到的页面数据来一直变动的,咱们能够从 httparchive 地址来查看当初世界上的页面的中位数是多少。
依据目前的指标来看,FCP 工夫能够简略的分为 3 档:
0 – 1.9s,1.9s – 3s,3s 以上,它们别离代表还行、很个别、不太行。
当咱们在某个页面中应用 LightHouse 进行评估的时候,可能会看到只管 FCP 只有 1s,显示的也是橙色标记。
LightHouse 里的得分是依据百分比来的。也就是说当你的 FCP 工夫,是所有页面中的前 10%,那么能够失去 90 分,前 1% 能够失去 99 分,失去 90-100 分才会是绿色。其余的几个指标也是同样的评判规范。
影响 FCP 的起因有很多,其中一个比拟常见的起因是自定义字体的加载,字体文件的加载须要肯定工夫,在字体文件加载实现之前,不同的浏览器会采纳不同的策略。
edge:在字体筹备好前应用零碎字体
Chrome:暗藏文本内容。如果 3s 后自定义字体还没筹备好,则应用零碎字体,直到字体筹备好,而后替换字体。
火狐:同 Chrome
safari:暗藏文本直到字体筹备好。
一个简略的方法是在 @font-face 演示里减少 font-display: swap
@font-face {
font-family: 'Pacifico';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/pacifico/v12/FwZY7-Qmy14u9lezJ-6H6MmBp0u-.woff2) format('woff2');
font-display: swap;
}
设置 swap 就是通知浏览器是应用该字体的文本应该立刻应用零碎字体进行显示,当自定义字体准备就绪后,替换零碎字体。
或者应用 prefetch / preload 来提前获取字体相干资源。
Time to intercative
TTI 标记着页面多久能够进行齐全交互。
这里的齐全交互是指:
- 页面曾经展现了 FCP
- 事件处理函数曾经为大部分可见页面元素进行了注册绑定
- 页面可能在 50ms 内对用户行为进行反馈
同样咱们在 httparchive 来看一下这个世界上网页 TTI 的中位数是多少。
升高 TTI 的常见思路是代码宰割、按需加载,删除未应用代码、压缩代码、压缩网络负载、缩小 JS 对主线程的长时间占用。
Speed Index
这个指标代表了用户感知的可见区域的页面加载的快慢。
也分成 0-3.4s、3.4 – 5.8、超过 5.8 三挡。然而得分也同样是跟寰球网页数据来比照的。
进步 speed index 的次要是通过缩小 js 对主线程阻塞,让一些非必要的 js 在 Dom 渲染后再执行。
Total Block Time
总阻塞工夫。这个工夫是从 FCP 到 TTI 之间所有的长工作阻塞局部的工夫之和。
长工作是指执行工夫超过 50ms 的工作,50ms 之后的工夫量就是阻塞工夫。
既然是阻塞工夫,升高 TBT 的方法就是想方法缩小不必要的 js 的加载、解析和执行。拆分大型脚本,对某些非同步必要的 js 应用 defer/async 或者 prefetch/preload、或者容许的状况下进行懒加载 / 提早加载、将动态资源部署到 CDN 等等。
Largest Contentful Paint
最大内容绘制。记录的是视口中最大的内容元素被渲染到屏幕的工夫,也大抵分为 0-2.5s、2.5s-4s、超过 4s 三个大范畴。
这里的类容元素是指:
- <img >
- <svg> 内嵌的 <image> 元素
- 应用了封面的 <video> 元素
- url() 加载的带背景图的元素
- 蕴含文本或者其余行内文本元素子元素的块级元素
留神,如果元素溢出到可视区域之外,则不算 LCP。
LCP 次要手 4 个方面的影响:
- 迟缓的服务器响应速度
应答计划:CDN、预加载、serviceWorker
- js/css 的渲染阻塞
应答计划:
1、用 optimize-css-assets-Webpack-plugin、uglyifyJS 之类的 Webpack 插件压缩 css、js
2、对非必要的 js、css 提早加载,如非必要 css 用预加载,在触发事件后再去 import xx from ‘xxx’。
3、适合的状况下应用内联 css。
- 迟缓的资源加载速度
- 压缩图像
- 预加载重要资源
- 压缩文本文件 Gzip、br
- serviceWorker 进行缓存
- 客户端渲染
- 压缩 js
- 提早加载未立刻应用的 js
- 尽可能减少 polyfill。”targets”:”>0.25%”
Cumulative Layout Shift
有些时候咱们会遇到,初始加载时字体突然变大 / 变小,元素地位忽然挪动位等。
CLS 就是通过测量产生偏移的频率来示意出页面的不稳定性。
常见的导致 CLS 比拟差的起因有:
- 没指定宽高的图片
- 没有设置宽高的 iframe
- 没有设置宽高的资源位(顶部 banner、广告等)
后面提到的 无款式文本闪动(FOUT,用默认字体替换新字体)/ 不可见文本闪动(FOIT,获取新字体前的显示不可见文本)。<link rel=preload> 和 font-display: optional 联合应用
- 动画应用了批改 width、height、top、right、bottom、left 等属性值的形式来实现。应优先应用 css transfrom 来实现动画。
以上就是目前 Chrome lighthouse 用来判断页面体验的 6 个指标。如果咱们要优化页面,也应从这 6 个方面来动手,逐个改良,在现有的可量化指标下对症下药。
参考资料:https://www.debugbear.com/blo…
举荐浏览
低代码是开发的将来吗?浅谈低代码平台
当议论 React hook,咱们到底说的是什么?