06627网页制作和网站建设试卷,dw制作网站,微网站和普通网站区别,柳州网站建设哪家页面是如何渲染的#xff1f;通常会得到“解析 HTML、css 合成 Render Tree#xff0c;就可以渲染了”的回答。但是具体都做了些什么#xff0c;却很少有人细说#xff0c;我们今天就从 Chrome 的性能工具开始#xff0c;具体看看一个页面是如何进行渲染的#xff0c;以及…页面是如何渲染的通常会得到“解析 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 lookupDNS 查询解析这个域名的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 和 WaitingTTFB的时间。深色的 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 引用的资源或是relprelaod 或 asstyle预加载的资源可能会被优先处理。而当资源是 prefetch或者用 这种方式的由于优先级低就会被延后下载。一般的其他资源则按顺序下载。
回到 Network可以看到在 www.bilibil.com 的箱线图之后是一连串 js、css、Webp 资源需要加载的请求被发起了。把鼠标移动到这些箱线图上会看到上面有优先级 lowest low high highest这就表示了资源的重要程度。 那么这些资源的优先级是如何评定的一般来说访问域名获取的 HTML、 以及预加载资源时as“style”拥有最高优先级。普通的
这种方式的和不加 asxxx的 prefetch 预加载就相当于异步加载拥有最低优先级。
HTML Parse
好到现在为止我们已经将用到的 js、css、图片等资源下载了然后就该进入解析 HTML 的过程了。
在 Chrome 渲染引擎内部有个 HTMLParser 的模块。HTML 解析器负责将 HTML 转化为 Dom 结构。HTML 解析器并不是等整个文档全部获取之后才开始解析而是加载了足够的数据后就开始解析了。
在 HTML Parser 的 summary 面板里可以看到有个Rangewww.bilibili.com[0…45]。点进去看一下可以发现定位到了 HTML 的 45 行。
这也从侧面印证了解析 HTML 的过程并不是一次全部执行完的。 HTML 的解析生成 Dom 树的过程可以参考文章https://medium.com/nybles/introduction-to-Dom-bee3b2dd9911。简单来说就是将字节流转换成 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 是不包含例如、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-changetransfrom 或者 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 等。如果你的页面里有 iframeiframe 里的任何东西都不会被当成 Dom content。
FCP 好坏标准也是随着收集到的页面数据来不断变化的我们可以从 httparchive 地址来查看现在世界上的页面的中位数是多少。 根据目前的指标来看FCP 时间可以简单的分为 3 档
0 - 1.9s1.9s - 3s3s以上它们分别代表还行、很一般、不太行。
当我们在某个页面中使用 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 三个大范围。
这里的类容元素是指 内嵌的 元素 使用了封面的 元素 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获取新字体前的显示不可见文本。和font-display: optional结合使用
动画使用了修改 width、height、top、right、bottom、left 等属性值的方式来实现。应优先使用 css transfrom来实现动画。
以上就是目前 Chrome lighthouse 用来判断页面体验的 6 个指标。如果我们要优化页面也应从这 6 个方面来入手逐一改进在现有的可量化指标下有的放矢。
参考资料https://www.debugbear.com/blog/devtools-performance