我们已经知道性能的重要性,并且认为网页应用的运行速度十分关键。
但是,当我们尝试回答“我的应用有多快?”这个问题时,您就会意识到,“快”是一个很模糊的概念,并且是相对的。
事实上在网页加载的任何时期,都可能发生性能不佳的情况,而不仅仅是加载的过程,所以我们不能仅仅从加载时间来判断网页的性能。
应用无法迅速响应用户的操作,无法平滑的滚动或者产生动画效果和加载缓慢一样,都会导致糟糕的用户体验
所以这些性能误解,会导致开发者将注意力集中在对用户体验帮助不大甚至完全无效的事情上。
因此,为了避免出现这个错误,我们来回答下列问题:
哪些指标能够最准确地衡量人所感受到的性能?
如何针对实际用户来衡量这些指标?
如何解读衡量结果以确定应用是否“速度快”?
了解应用的实际用户性能之后,如何避免性能下降并在未来提高性能?
当用户导航到网页时,通常会寻找视觉反馈,以确信一切合预期。
是否发生? 导航是否成功启动?服务器是否有响应?
是否有用? 是否已渲染可以与用户互动的足够内容?
是否可用? 用户可以与页面交互,还是页面仍在忙于加载?
是否令人愉快? 交互是否顺畅而自然,没有滞后和卡顿?
为了解页面何时为用户提供这样的反馈,我们定义了多个新指标:
FCP First Contentful Paint 首次内容绘制
FMP First Meaningful Paint 首次有效绘制
Speed Index 速度指数
First CPU Idle 首次CPU闲置时间
TTI Time to Interactive可交互前耗时
FID Max Potential First Input Delay首次输入延迟最大预估
将指标对应到用户体验
回顾上文确定的对用户体验最重要的问题,下表概述刚刚列出的各个指标如何对应到我们希望优化的体验:
下列加载时间线屏幕截图有助于您更直观地了解加载指标对应的加载体验:
体验 指标
是否发生? 首次绘制 (FP)/首次内容绘制 (FCP)
是否有用? 首次有效绘制 (FMP)/主角元素计时
是否可用? 可交互时间 (TTI)
是否令人愉快? 耗时较长的任务(在技术上不存在耗时较长的任务)
FP 首次绘制
FCP首次内容绘制的时间
Paint Timing API 定义两个指标:首次绘制 (FP) 和 首次内容绘制 (FCP)。 这些指标用于标记导航之后浏览器在屏幕上渲染像素的时间点。 这对于用户来说十分重要,因为它回答了以下问题: 是否发生?
这两个指标之间的主要差别在于:
FP 标记浏览器渲染任何在视觉上不同于导航前屏幕内容之内容的时间点。 FCP 标记的是从导航浏览器渲染来自 DOM 第一个字节内容的时间,该内容可能是文本、图像、SVG 甚至
问:从浏览器导航栏输入网址到展示内容发生了什么?
它反馈了页面的加载时间
那么如何去减少加载时间,可以从下两个方面
加快资源下载时间
减少阻塞渲染DOM内容的工作
具体可以做的工作:
减少外部的css 和 js 依赖
使用http缓存加速重复访问
减小压缩资源
js 的启动优化减少js的荷载,在页面加载时候减少加载js 例如:Tree-shaking(Tree shaking is a form of dead code elimination)
可以通过以下方法降低 JavaScript 的网络传输成本:
· 仅发送用户所需的代码。
· 使用代码拆分将 JavaScript 分解成关键部分和非关键部分。
· 延迟加载非关键代码。
· 源码压缩·
· 移除未使用的代码。·
· 缓存代码以最大限度减少网络往返次数。
· 使用长期缓存以避免重新提取尚未更改的资源。
追踪 FCP
查看代码片段是如何以编程的方式去访问 FCP 数据
FMP首次有效绘制和主角元素计时
目前尚无标准化的 FMP 定义,因此也没有性能条目类型。 部分原因在于很难以通用的方式确定“有效”对于所有页面意味着什么。
但是,一般来说,在单个页面或单个应用中,最好是将 FMP 视为主角元素呈现在屏幕上的时刻。
首次有效绘制 (FMP) 指标能够回答“是否有用”这一问题。
虽然“有用”这一概念很难以通用于所有网页的方式规范化(因此尚不存在任何规范),但是网页开发者自己很清楚其页面的哪些部分对用户最为有用。
网页的这些“最重要部分”通常称为主角元素。 例如 在天气应用上,主角元素是指定地点的天气预测。 在新闻网站上,主角元素可能是重大新闻和置顶大图。
在网页上,几乎总有一部分内容比其他部分更重要。 如果页面最重要的部分能迅速加载,用户可能不会注意到其余部分是否加载。
Speed Index
Speed Index是一个页面加载的性能指标,展示页面内容额可见填充速度。数值越低越好
要降低速度指数:
优化内容效率。 删除无无用的下载,压缩
优化关键渲染路径。
First CPU Idle
第一个CPU空闲指标衡量一个页面是可以互动的最低时间限度
优化策略:
在页面加载前减少必须要下载或执行的关键资源
减少每个关键资源的大小
TTI 可交互时间
可交互时间 (TTI) 指标用于标记应用已进行视觉渲染并能可靠响应用户输入的时间点,衡量的是页面完全可以交互。
应用可能会因为多种原因而无法响应用户输入:
页面组件运行所需的 JavaScript 尚未加载。
耗时较长的任务阻塞主线程
TTI 指标可识别页面初始 JavaScript 已加载且主线程处于空闲状态(没有耗时较长的任务)的时间点。
为什么要使用TTI来衡量?
因为又些网站是以牺牲用户的交互体验来换取页面的展示。当你发现页面已经展示好了,但是不能进行交互,这是很令人沮丧的。
注:完全可交互的几个指标:
1、页面已经展示了已经被FCP测量的有用的内容,
2、大多数的可见元素已经绑定了事件
3、页面在50ms内响应用户的交互
First CPU Idle 和 TTI 都是衡量用户是否可以在页面进行输入
First CPU Idle 发生在用户可以开始和页面交互
TTI 发生在用户可以完全的和页面进行交互
计算 TTI 的得分
提高TTI
延迟或者删除不必要的js
代码拆分
优化第三方的js
减少主线程工作
减少js 的执行时间
Max Potential FID
最差情况用户体验的输入延迟
从第一次用户和网站进行交互到浏览器真正去回应这个交互的时间。
这个时间是根据在FCP之后的最长任务( longest task)
的时间
为什么是FCP 之后的最长任务是因为。在FCP 之前用户不会去尝试和没有任何内容的页面进行交互。这是FCP改衡量的。
如何计算FID的得分,是和 HTTP Archive
如何提高得分:
如何提高得分:
和提高TTI 是一致的
FCP / FMP / Speed Index
都是用来衡量页面内容的加载和渲染的性能指标
First CPU Idle / TTI / FID
都是用来衡量用户能否交互的指标
耗时较长的任务
浏览器通过将任务添加到主线程上的队列等待逐个执行来响应用户输入。 浏览器执行应用的 JavaScript 时也会这样做,因此从这个角度看,浏览器为单线程。
在某些情况下,运行这些任务可能要花费较长时间,如果确实如此,主线程就会遭到阻止,而队列中的所有其他任务都必须等待。
对于用户而言,任务耗时较长表现为滞后或卡顿,而这也是目前网页不良体验的主要根源。
Long Tasks API 可以将任何耗时超过 50 毫秒的任务标示为可能存在问题,并向应用开发者显示这些任务。 选择 50 毫秒的时间是为了让应用满足在 100 毫秒内响应用户输入
浏览器通过将任务添加到主线程上的队列等待逐个执行来响应用户输入。 浏览器执行应用的 JavaScript 时也会这样做,因此从这个角度看,浏览器为单线程。
在某些情况下,运行这些任务可能要花费较长时间,如果确实如此,主线程就会遭到阻止,而队列中的所有其他任务都必须等待。
对于用户而言,任务耗时较长表现为滞后或卡顿,而这也是目前网页不良体验的主要根源。
Long Tasks API 可以将任何耗时超过 50 毫秒的任务标示为可能存在问题,并向应用开发者显示这些任务。 选择 50 毫秒的时间是为了让应用满足在 100 毫秒内响应用户输入的 RAIL 指导原则。
优化内容效率
优化关键渲染路径
RAIL 模型评估
Rail 是一种以用户往往为中心的性能模型
每个网络应用都有与起生命周期相关的四个不同的方向,并且这些方向以不同的方式影响着性能
以用户未中心:
以用户未未中心不说让我们的网站在任何特定的设备都能运行很快,而是使用户满意
立即响应用户: 在100ms 内确定用户的输入
设置动画或者滚动,在10ms 内生成帧
最大限度增加主线程的空闲时间
持续吸引用户;在1000ms呈现交互内容
以用户为中心:
让用户成为您的性能工作的中心。用户花在网站上的大多数时间不是等待加载,而是在使用时等待响应。了解用户如何评价性能延迟:
响应:在 100 毫秒以内响应
在用户注意到滞后之前您有 100 毫秒的时间可以响应用户输入。这适用于大多数输入,不管他们是在点击按钮、切换表单控件还是启动动画。但不适用于触摸拖动或滚动。
如果您未响应,操作与反应之间的连接就会中断。用户会注意到。
尽管很明显应立即响应用户的操作,但这并不总是正确的做法。使用此 100 毫秒窗口执行其他开销大的工作,但需要谨慎,以免妨碍用户。如果可能,请在后台执行工作。
对于需要超过 500 毫秒才能完成的操作,请始终提供反馈。
动画:在 10 毫秒内生成一帧
动画不只是奇特的 UI 效果。例如,滚动和触摸拖动就是动画类型。
如果动画帧率发生变化,您的用户确实会注意到。您的目标就是每秒生成 60 帧,每一帧必须完成以下所有步骤:
从纯粹的数学角度而言,每帧的预算约为 16 毫秒(1000 毫秒 / 60 帧 = 16.66 毫秒/帧)。 但因为浏览器需要花费时间将新帧绘制到屏幕上,只有 10 毫秒来执行代码
在像动画一样的高压点中,关键是不论能不能做,什么都不要做,做最少的工作。 如果可能,请利用 100 毫秒响应预先计算开销大的工作,这样您就可以尽可能增加实现 60fps 的可能性。
跟踪耗时较长的任务
上文提到,耗时较长的任务通常会带来某种负面的用户体验(例如, 事件处理程序运行缓慢,或者掉帧)。 您最好了解发生这种情况的频率,以设法尽量减少这种情况。
要在 JavaScript 中检测耗时较长的任务,请创建新的 PerformanceObserver,并观察类型为 longtask 的条目。 耗时较长的任务条目的一个有点是包含提供方属性,有助于您更轻松地追查导致出现耗时较长任务的代码:
1 | const observer = new PerformanceObserver((list) => { |
observer.observe({entryTypes: [‘longtask’]});
提供方属性会指出导致耗时较长任务的帧上下文,这有助于您确定问题是否由第三方 iframe 脚本所致。 未来版本的规范计划添加更多详细信息并公开脚本网址、行号和列号,这有助于确定速度缓慢问题是否由您自己的脚本所致。
跟踪输入延迟
阻塞主线程的耗时较长任务可能会导致事件侦听器无法及时执行。 RAIL 性能模型指出,为提供流畅的界面体验,界面应在用户执行输入后的 100 毫秒内作出响应,若非如此,请务必探查原因。
若要在代码中检测输入延迟,您可将事件时间戳与当前时间作比较,如果两者相差超过 100 毫秒,您可以并应该进行报告。
1 | const subscribeBtn = document.querySelector('#subscribe'); |
由于事件延迟通常是由耗时较长的任务所致,因此您可将事件延迟检测逻辑与耗时较长任务检测逻辑相结合:如果某个耗时较长的任务在 event.timeStamp 所示的时间阻塞主线程,您也可以报告该耗时较长任务的提供方值。 如此,您即可在负面性能体验与导致该体验的代码之间建立明确的联系。
虽然这种方法并不完美(不会处理之后在传播阶段耗时较长的事件侦听器,也不适用于不在主线程中运行的滚动或合成动画),但确实是良好的开端,让您能更好地了解运行时间较长的 JavaScript 代码对用户体验产生影响的频率。
移动设备
百分位 TTI(秒)
50% 3.9
75% 8.0
90% 12.6
按移动设备与桌面设备划分结果,并以分布图的方式分析数据,可让您迅速了解实际用户的体验。 例如,通过上表,我很容易便可发现,对于此应用,10% 的移动用户需等待 12 秒以上才能进行交互!
性能对业务的影响
在分析工具中跟踪性能有一项巨大优势,即您之后可以利用该数据来分析性能对业务的影响。
如果您在分析工具中跟踪目标达成情况或电子商务转化情况,则可通过创建报告来探查两者与应用性能指标之间的关联。 例如:
体验到更快交互速度的用户是否会购买更多商品?
如果用户在结账流程中遇到较多耗时较长的任务,其离开率是否较高?
如果发现存在关联,即可轻松建立性能至关重要且应该优先考虑的商业案例。
加载放弃
我们知道,用户经常会因为页面加载时间过长而选择离开。 不幸的是,这意味着我们的所有性能指标都存在幸存者偏差,即数据不包括未等待页面加载完成的用户的加载指标(这很可能意味着数量过低)。
虽然您无法获悉如果这类用户逗留所产生的指标,但可以跟踪发生这种情况的频率以及每位用户逗留的时长。
使用 Google Analytics 完成此任务比较棘手,因为 analytics.js 库通常是以异步方式加载,而且可能在用户决定离开前尚不可用。 但是,您无需等待 analytics.js 加载完成再将数据发送到 Google Analytics。 您可以直接通过 Measurement Protocol 发送数据。
以下代码为 visibilitychange 事件添加侦听器(该事件在页面卸载或进入后台时触发),并在该事件触发时发送 performance.now() 值。
优化性能以及避免性能下降
定义以用户为中心的指标的好处在于,您优化这些指标时,用户体验必然也会同时改善。
改善性能最简单的一种方法是,直接减少发送到客户端的 JavaScript 代码,但如果无法缩减代码长度,则务必要思考如何提供 JavaScript。
优化 FP/FCP
从文档的
优化 FMP/TTI
确定页面上最关键的界面元素(主角元素)之后,您应确保初始脚本加载仅包含渲染这些元素并使其可交互所需的代码。
初始 JavaScript 软件包中所包含的任何与主角元素无关的代码都会延长可交互时间。 没有理由强迫用户设备下载并解析当前不需要的 JavaScript 代码。
一般来说,您应该尽可能缩短 FMP 与 TTI 之间的时间。 如果无法最大限度缩短此时间,界面绝对有必要明确指出页面尚不可交互。
避免出现耗时较长的任务
拆分代码并按照优先顺序排列要加载的代码,不仅可以缩短页面可交互时间,还可以减少耗时较长的任务,然后即有希望减少输入延迟及慢速帧。
除了将代码拆分为多个单独的文件之外,您还可将大型同步代码块拆分为较小的块,以便以异步方式执行,或者推迟到下一空闲点。 以异步方式在较小的块中执行此逻辑,可在主线程中留出空间,供浏览器响应用户输入。
最后,您应确保测试第三方代码,并对任何低速运行的代码追责。 产生大量耗时较长任务的第三方广告或跟踪脚本对您业务的伤害大于帮助。
避免性能下降
本文重点强调的是针对实际用户的性能测量,虽然 RUM 数据确实是十分重要的性能数据,但实验室数据对于在发布新功能前确保应用性能良好(而且不会下降)而言仍然十分关键。 实验室测试非常适合用于检测性能是否下降,因为这些测试是在受控环境中运行,出现随机变化的可能性远低于 RUM 测试。
Lighthouse 和 Web Page Test 等工具可以集成到持续集成服务器中,而且您可以编写相应的测试,以在关键指标退化或下降到低于特定阈值时将构件判定为失败。
对于已发布的代码,您可以添加自定义提醒,以通知您负面性能事件的发生率是否意外突增。 例如,如果第三方发布其某个服务的新版本,而您的用户突然开始看到大量新增的耗时较长任务,就代表出现这种情况。
为成功避免性能下降,您必须在实验室和实际运行环境中针对发行的每个新功能进行性能测试。
【参考】
1、User-centric performance metrics by Philip Walton