关于动画流畅性指标

了解如何测量动画,如何看待动画帧,以及页面的整体流畅度。

Behdad Bakhshinategh
Behdad Bakhshinategh
Jonathan Ross
Jonathan Ross
Michal Mocny
Michal Mocny

您可能会遇到网页“卡顿”或“冻结”滚动屏幕期间 动画。我们常说,这些体验并不流畅。收件人地址 Chrome 团队一直致力于为 并不断改进用于动画检测的实验室工具 Chromium 中的渲染流水线诊断。

我们想分享一些近期进展,提供具体的工具指导, 讨论未来动画平滑度指标的想法。与往常一样,我们希望 听取您的反馈

本博文将涵盖三大主题:

  • 快速了解动画和动画帧。
  • 我们目前关于衡量整体动画平滑度的想法。
  • 为今天的实验工具提供一些实用建议。

什么是动画?

动画让内容更生动鲜活!让内容动起来,尤其是在回应用户时 动画可以使体验感觉更自然, 简单易懂、趣味性强

但如果动画实现不当,或者只是添加过多的动画, 就会降低体验的难度,使游戏明显变得毫无趣味。我们可能都遇到过 与刚刚添加了过多“有用”的界面互动过渡效果 而实际上,如果广告效果不佳,则会导致玩家难以获得其体验。 因此有些用户 首选减少动作,一种用户偏好

动画如何运作?

让我们快速回顾一下,渲染流水线 由几个依序阶段组成:

  1. 样式:计算 应用到元素的样式
  2. 布局:生成 几何图形和位置。
  3. 画图:填写 每个元素的像素组合成层。
  4. 复合材料:绘制 叠加到屏幕上。

虽然定义动画的方式多种多样,但它们都基本通过 以下项之一:

  • 调整布局 属性。
  • 调整绘制 属性。
  • 调整复合 属性。

由于这些阶段是依序进行的,因此务必要在 属性。更新时间越早 费用越高,就越难避免 平滑。(请参阅渲染 效果 了解详情。)

虽然为布局属性添加动画效果很方便,但 即使这些成本并不能立即显现出来。动画应 尽可能根据复合属性更改进行定义。

定义声明式 CSS 动画或使用网页 动画、 并确保为合成动画 媒体资源, 是确保动画流畅、高效的第一步。不过, 但单靠这一点并不能保证流畅度,因为即使是高效的网页动画, 存在性能限制因此,衡量始终至关重要!

什么是动画帧?

网页的视觉呈现效果更新需要一些时间才会显示。图像 都会产生一个新的动画帧,该帧最终渲染在 用户的显示屏。

按一定的时间间隔显示更新,以便对视觉更新进行批量处理。许多显示屏 以固定的时间间隔进行更新,如每秒 60 次(即 60 Hz)。某些更现代的显示屏可以提供更高的刷新率 (90–120 Hz 越来越常见)。这些显示器通常可以主动适应 甚至提供完全可变的帧速率

任何应用(如游戏或浏览器)的目标都是处理所有这些 批量视觉更新,并在 都能做到这一点请注意,该目标完全不同于 重要的浏览器任务,例如从网络快速加载内容,或 高效执行 JavaScript 任务。

在某些情况下,在一个区域内完成所有视觉更新可能会变得非常困难 显示屏指定的截止期限。在这种情况下,浏览器会 会丢弃一帧。屏幕不会变黑,只是在不断重复。你看, 同一个视觉更新,而且持续时间较长 - 即与界面上显示的同一个动画帧 呈现的内容

这种情况经常发生!它不一定能察觉到,尤其是 用于静态或类似文档的内容,这在 Web 平台上很常见, 仅当存在重要的视觉元素时,丢帧才会显现出来 更新,例如动画,对于此类更新,我们需要稳定的动画流 更新以显示流畅的动作。

哪些因素会影响动画帧?

Web 开发者可以极大地影响浏览器快速、 高效地渲染和呈现视觉更新!

一些示例:

  • 使用过大或占用大量资源,以致无法在视频上快速解码的内容 目标设备。
  • 使用过多 层 需要过多的 GPU 内存。
  • 定义过于复杂的 CSS 样式或网页动画。
  • 使用会停用快速渲染优化的设计反模式。
  • 主线程上工作的 JS 过多,导致任务耗时长,妨碍视觉 更新。

但是,您如何知道动画帧何时错过了截止时间,并导致了 丢帧?

一种可能的方法是使用 requestAnimationFrame() 但也存在一些缺点requestAnimationFrame(),即“rAF”, 用于告知浏览器您希望执行动画,并请求 在渲染管道的下一个绘制阶段之前就有机会执行此操作。如果 您的回调函数没有在您想要的时间被调用,这意味着 未执行绘制,并且跳过了一个或多个帧。通过轮询和 您可以计算一种“每秒帧数” (FPS) 指标。

let frameTimes = [];
function pollFramesPerSecond(now) {
  frameTimes = [...frameTimes.filter(t => t > now - 1000), now];
  requestAnimationFrame(pollFramesPerSecond);
  console.log('Frames per second:', frameTimes.length);
}
requestAnimationFrame(pollFramesPerSecond);

最好不要使用 requestAnimationFrame() 轮询,原因如下:

  • 每个脚本都必须设置自己的轮询循环。
  • 它可能会阻塞关键路径。
  • 即使 rAF 轮询速度很快, requestIdleCallback() 连续使用时,无法调度很长的空闲块( 超过单帧)。
  • 同样,缺少长时间空闲块会阻止浏览器调度其他 长时间运行的任务(如较长的垃圾回收和其他后台或 是推测性工作)。
  • 如果轮询开启和关闭,就会错过帧预算 上限。
  • 如果浏览器使用的是假正例,那么轮询会报告假正例 可变更新频率(例如,由于电源或可见性状态而中断)。
  • 最重要的是,它无法捕捉到所有类型的动画, 更新!

在主线程上执行太多工作可能会影响查看动画帧的能力。 查看卡顿 示例,了解 一旦主线程上的工作量过大(例如 会导致丢帧、rAF 回调减少,并降低 FPS。

当主线程出现卡顿时,视觉更新开始卡顿。 那就是卡顿!

许多衡量工具都非常关注主要衡量工具 以便及时让出,并确保动画帧顺畅运行。 但事实并非如此!请参考以下示例:

上面的视频显示了一个页面,该页面定期将较长的任务注入主 线程。这些耗时较长的任务完全破坏了网页提供 某些类型的视觉更新,您可以在左上角看到 requestAnimationFrame() 对应的 FPS 报告为 0。

但是,尽管执行这些耗时较长的任务,页面仍会继续平稳地滚动。这个 是因为在现代浏览器中,滚动操作通常 消息串式、 完全由合成器驱动。

这个例子是主视频上同时含有许多丢帧 但在一个线程上仍有许多已成功传送的滚动帧 合成器线程。当这个耗时较长的任务完成后,主线程绘制更新 反之也没有任何外观变化rAF 轮询建议将帧丢失 但从视觉上看,用户是不会注意到的!

对于动画帧而言,故事没有那么简单。

动画帧:重要的更新

上面的例子表明,故事不仅仅局限于 requestAnimationFrame()

那么,动画更新和动画帧何时适用?以下是 一些我们正在考虑的标准,并期待收到您的反馈意见:

  • 主线程和合成器线程更新
  • 缺少绘制更新
  • 检测动画
  • 质量还是数量

主线程和合成器线程更新

动画帧更新不是布尔值。并不是 可以完全放下或完全呈现导致动画的原因有很多 帧可能会部分 呈现。也就是说,它可以同时 一些过时的内容,同时还有一些新的视觉更新, 呈现的内容。

最常见的一个例子是浏览器无法生成新的 主线程在帧截止时间内更新,但有新的合成器线程 update(如之前的线程式滚动示例)。

使用声明式动画为合成动画添加动画的一个重要原因 属性,因为这样做可以让动画 完全由合成器线程处理,即使主线程处于忙碌状态也无妨。这些类型 的动画可以继续以高效的方式持续生成视觉更新, 并行运行。

另一方面,在某些情况下,主线程更新最终可能变成 只有在错过了几个帧的截止时间后,才能用于演示。在这里 浏览器会进行一些新的更新,但可能不是最新的

一般而言,我们会考虑包含一些新视觉更新的 部分帧,而不是所有新的视觉更新。部分帧 通用。理想情况下,部分更新至少会包括 视觉更新,例如动画,但仅当动画 由合成器线程驱动

缺少绘制更新

另一种类型的部分更新是图片等媒体内容尚未完成 解码和光栅化,以便呈现帧。

或者,即使网页完全是静态的,浏览器在呈现方面仍有可能落后 快速滚动时的外观更新。这是因为 可舍弃超出可见视口的内容以节省 GPU 内存。它 渲染像素需要时间,而渲染一帧所需的时间可能长于一帧。 它会在用户进行大幅度的滚动操作(例如滑动手指)后呈现所有内容。这通常是 称为棋盘格

对于每个帧呈现机会,您都可以跟踪 屏幕显示最新的视觉更新衡量执行此操作的能力 通常称为帧吞吐量

如果 GPU 确实出现卡顿,浏览器(或平台)甚至可能开始 限制了它尝试视觉更新的速率,从而降低 有效帧速率虽然从技术层面来讲,这可以减少 帧更新,从视觉上看它仍然显示为较低的帧吞吐量。

然而,并非所有类型的低帧吞吐量都是糟糕的。如果网页大部分时间处于空闲状态 并且没有活动的动画,低帧速率 非常有吸引力(而且可以节省电量!)。

那么,帧吞吐量在什么情况下才算重要呢?

检测动画

高帧吞吐量尤其重要,尤其是在重要时刻 动画。不同的动画类型都依赖于 特定线程(主线程、合成器或工作器),因此其视觉更新 依赖于在截止时间内提供更新的线程。我们说 只要存在符合以下条件的有效动画,给定线程就会影响流畅度: 取决于该线程更新。

某些类型的动画比其他类型的动画更容易定义和检测。 声明式动画或由用户输入驱动的动画定义起来更清晰 而非由 JavaScript 驱动的动画,通过定期更新来呈现动画效果 样式属性。

即便你有requestAnimationFrame() 无法始终假定每次 rAF 调用必定会产生 更新或动画。例如,仅使用 rAF 轮询来跟踪帧速率 (如上所示)本身不应该影响平滑度测量, 没有视觉更新。

质量还是数量

最后,检测动画和动画帧更新只是 故事中,它仅捕获动画更新的数量, 质量。

例如,您在观看 视频。从技术上讲,这个过程非常流畅,但视频本身可能有 比特率较低或网络缓冲问题这并非 动画平滑度指标,但对于 用户。

或者,利用 <canvas> 的游戏(甚至可能使用 屏幕外 画布至 确保稳定的帧速率)从技术角度而言, 同时无法将高质量的游戏资源加载到 或呈现伪影。

当然,网站可能只是有一些非常糟糕的动画 🙂?

正在建设中的老派 GIF

我想他们当时还挺酷的!

单个动画帧的状态

由于可能只呈现部分帧,或者丢帧可能以各种方式 我们已经开始将每个帧看作 完整性或流畅性得分。

我们通过多种方式解读单个事件 动画帧,按照从最佳到最差的顺序进行排序:

不需要更新 空闲时间,重复上一帧。
完全呈现 主线程更新要么在截止期限内提交,要么没有 需要主线程更新。
部分呈现 仅合成器;延迟的主线程更新 更改。
部分呈现 仅合成器;主线程有视觉更新 更新未包含影响平滑度的动画。
部分呈现 仅合成器;主线程有视觉更新, 但系统还是会使用之前过时的帧。
部分呈现 仅合成器;没有必要的主要更新,而 合成器更新有一个影响平滑度的动画。
部分呈现 仅合成器,但合成器更新不含 影响平滑度的动画。
丢帧 无更新。无需更新合成器,主要内容为 延迟。
丢帧 需要进行合成器更新,但延迟。
过时的帧 需要更新,它是由渲染程序生成的,但 GPU 在 vsync 截止时间之前仍未显示。

您可以将这些状态转换为一定的分数。或许有一种方法 要解读此得分,只需将它视为由以下因素观测到的概率 用户。单个丢帧可能不太容易被观测到, 许多丢帧会影响连续的流畅度!

综合应用:丢帧百分比指标

虽然有时可能需要深入了解 指定快速“一目了然”的动画帧得分 体验

因为系统可能会部分呈现帧,甚至会跳过全部帧 帧更新实际上并不会影响流畅度,因此我们希望减少重点放在 只计算帧数,以及浏览器无法做到这一点的程度 适时提供直观的完整更新。

心理模型应从:

  1. 每秒帧数
  2. 检测缺失和重要的动画更新,
  3. 给定时间段内的下降百分比

重要的是:用户等待重要邮件所占的比例 更新。我们认为这与用户自然流畅的使用体验相吻合 网络内容的实际运用到目前为止,我们一直使用以下内容作为 初始指标集:

  • 平均丢弃百分比:针对整个播放过程中的所有非空闲动画帧 整个时间轴
  • 最糟糕的丢帧百分比案例:以 1 秒滑动屏幕进行衡量 。
  • 丢弃帧百分比的第 95 个百分位:根据 1 秒的测量结果计算得出 滑动时间窗口

目前,您可以在一些 Chrome 开发者工具中查看这些分数。虽然这些 指标只关注整体帧吞吐量,我们还在评估 等因素。

在开发者工具中亲自尝试一下吧!

性能 HUD

Chromium 上有一个整齐的 Performance HUD 隐藏在旗帜后面 (chrome://flags/#show-performance-metrics-hud)。在这里,您可以找到 Core Web Vitals 等指标以及一些实验性定义 一段时间内根据丢弃的帧百分比得出的动画平滑度。

性能 HUD

帧渲染统计信息

启用“帧渲染” 统计信息” 在开发者工具中通过“渲染设置”查看新动画帧的实时视图, 这些帧以不同的颜色标识,用于区分部分更新和完全丢弃的帧 更新。报告的 fps 仅适用于完全呈现的帧。

帧渲染统计信息

开发者工具性能分析记录中的 Frame Viewer

DevTools 性能 面板 很久以前有 Frames 查看器。 但是,它与现代渲染管道的方式有点不同步。 效果。即使是在最新的 Chrome Canary 版,我们认为这可以极大地简化动画问题的调试过程。

今天,您会发现相框查看器中的相框与 vsync 边界,并根据状态进行颜色编码。仍然没有满分 上述所有细微差别,但我们计划在 Google Cloud 中 。

Chrome 开发者工具中的 Frame Viewer

Chrome 跟踪记录

最后,Chrome Tracing 是深入探究细节的首选工具, 您可以录制“Web 内容呈现”通过新的 Perfetto 界面 界面(即 about:tracing),并深入了解 Chrome 的 图形管道。这可能是一项艰巨的任务,但有几个 最近已添加到 Chromium 中,以简化操作。您可以大致了解什么是 可能在客户生命周期中 框架 文档。

通过跟踪事件,您可以明确确定:

  • 正在运行的动画(使用名为 TrackerValidation 的事件)。
  • 获取动画帧的确切时间轴(使用名为 PipelineReporter)。
  • 对于卡顿的动画更新,请准确确定 加快动画的运行速度(使用 PipelineReporter 事件)。
  • 对于输入驱动的动画,请查看获取视觉更新所需的时间 (使用名为 EventLatency 的事件)。

Chrome 跟踪流水线报告程序

后续步骤

网页指标计划旨在提供关于 如何在 Web 上打造出色的用户体验。 基于实验室的指标,如总计 阻塞时间 (TBT) 对于 发现和诊断潜在的互动问题。我们计划 设计一个类似的基于实验室的动画平滑度指标。

随着我们继续研究如何设计 基于各个动画帧数据的综合指标。

将来,我们还希望设计一些 API, 为真实用户带来流畅的动画效果 字段以及实验中。 敬请关注此页面的最新动态!

反馈

我们很高兴看到这些最新的改进和开发者工具 用于衡量动画平滑度。不妨试试这些工具 对你的动画进行基准测试,并告诉我们你的动画效果如何!

您可以将自己的评论发送至 web-vitals-feedback Google 包含“[平滑度指标]”的组。我们真诚期待 期待听到您的想法!