先确认性能指标。例如 FCP、FP、FPS、平均请求时间等。

这些性能监控的结果,能够展现前端性能的好坏,根据性能监测的结果能够进一步的去优化前端性能,尽量的提升用户体验。

性能指标的计算

FPS 与 卡顿

FPS(Frames Per Second):每秒显示帧数。一般 FPS 在 60 以上,页面流畅,不卡顿。

计算 FPS 的思路:

关键:算出一帧 占 多少毫秒

宏任务——> 微任务——> requestAnimationFrame ——> 渲染 | ——> 宏任务 ——>

假设页面加载用时 X ms,这期间 requestAnimationFrame 执行了 N 次, 则帧率为 1000 * N/X,也就是 FPS

  1. /**
  2. * 不同的客户端有差异,考虑一下 requestAnimationFrame 的兼容性
  3. * 在不支持 requestAnimationFrame 时,使用 setTimeout 来模拟实现
  4. */
  5. var requestAnimationFrameCompatibility = (function () {
  6. return (
  7. window.requestAnimationFrame ||
  8. window.webkitRequestAnimationFrame ||
  9. window.mozRequestAnimationFrame ||
  10. function (callback) {
  11. window.setTimeout(callback, 1000 / 60)
  12. }
  13. )
  14. })()
  15. var fpsConfig = {
  16. lastFrameTime: performance.now(), // 记录上次统计帧数的时间
  17. count: 0 // x ms 内,执行了 count 次 requestAnimationFrame
  18. }
  19. // 计算 FPS 的值
  20. var fpsLoop = function () {
  21. var now = performance.now()
  22. fpsConfig.count += 1
  23. if (now > (1000 + fpsConfig.lastFrameTime)) {
  24. var fps = Math.round((1000 * fpsConfig.count) / (now - fpsConfig.lastFrameTime))
  25. console.log(`FPS 值为 ${fps}`);
  26. fpsConfig.lastFrameTime = now
  27. fpsConfig.count = 0 // 重置 count
  28. }
  29. requestAnimationFrameCompatibility(fpsLoop)
  30. }
  31. fpsLoop()

卡顿不能单纯用单次的 FPS 来衡量:FPS 低于 60 并不意味着卡顿,那 FPS 高于 60 也非一定不卡顿。 比如前 60 帧渲染很快(10ms 渲染 1 帧),后面的 3 帧渲染很慢( 20ms 渲染 1 帧),这样平均起来 FPS 为95,高于 60 的标准。这种情况会不会卡顿呢?明显是卡顿的。

我们用多次的 FPS 来衡量卡顿情况:如果有 3 次连续 FPS < 20,则说明卡顿。

FPS < 20,意味着每秒渲染 20 帧,卡到爆了。

  1. /**
  2. * 判断是否卡顿
  3. * @param {Array<number>} fpsList 记录了 FPS 的数组
  4. * @param {Number} below 低于某个值
  5. * @param {Number} last 持续几次
  6. */
  7. function isBlocking(fpsList, below, last) {
  8. var count = 0
  9. for (let i = 0; i < fpsList.length; i += 1) {
  10. if (fpsList[i] && fpsList[i] < below) count += 1
  11. else count = 0
  12. if(count > last) return true
  13. }
  14. return false
  15. }

FP 与 白屏

浏览器的页面加载过程:客户端发起请求 -> 下载 HTML 及 JS/CSS 资源 -> 解析 JS 执行 -> JS 请求数据 -> 客户端解析 DOM 并渲染 -> 下载渲染图片-> 完成整体渲染。

客户端解析 DOM 并渲染之前的时间就是白屏时间

navigationStart 和 fetchStart 的区别: navigationStart 是在上一个文档卸载之后,开始计算的。(即切换 tab 栏) fetchStart 是在发起请求时开始计算的。 如果没有上一个文档,navigationStart = fetchStart

  1. // 白屏时间 = 页面开始展示时间点 - 开始请求时间点
  2. const FP = performance.timing.domLoading - performance.timing.navigationStart

App 的页面加载过程:初始化 WebView -> 客户端发起请求 -> 下载 HTML 及 JS/CSS 资源 -> 解析 JS 执行 -> JS 请求数据 -> 服务端处理并返回数据 -> 客户端解析 DOM 并渲染 -> 下载渲染图片 -> 完成整体渲染。

App 下的白屏时间,多了启动浏览器内核,也就是 Webview 初始化的时间。在 App 测试版本中,程序在 App 创建 WebView 时打一个点,然后在开始建立网络连接打一个点,这两个点的时间差就是 Webview 初始化的时间。

FCP 与 首屏

首次内容绘制(首屏)FCP(First Contentful Paint):这个指标用于记录页面首次绘制文本、图片、非空白 Canvas 或 SVG 的时间。

  1. new Promise((resolve, reject) => {
  2. new PerformanceObserver((list) => {
  3. resolve(list);
  4. }).observe({ entryTypes: ["paint"] })
  5. }).then((list) => {
  6. list.getEntries().forEach((entry) => {
  7. console.log(`${entry.name}: ${entry.startTime}`);
  8. })
  9. }).catch((error) => {
  10. console.warn(error);
  11. })