前端性能监控:如何收集并分析首次内容绘制(FCP)、最大内容绘制(LCP)和首次输入延迟(FID)等核心性能指标。

前端性能监控:核心指标收集与分析

大家好,今天我们来聊聊前端性能监控,特别是如何收集和分析三个核心指标:首次内容绘制 (FCP)、最大内容绘制 (LCP) 和首次输入延迟 (FID)。这些指标直接关系到用户的感知性能,优化它们对于提升用户体验至关重要。

1. 为什么关注 FCP、LCP 和 FID?

在过去,我们常常使用 Page Load Time (PLT) 作为衡量页面性能的唯一标准。但 PLT 无法反映用户在页面加载过程中的真实感受。用户可能不需要等到所有资源都加载完毕,就能与页面进行交互。因此,FCP、LCP 和 FID 提供了更细粒度、更以用户为中心的性能视角。

  • FCP (First Contentful Paint): 首次内容绘制,标记了浏览器首次渲染任何文本、图像、非空白 canvas 或 SVG 的时间点。它告诉我们用户何时首次看到页面上的任何内容,是用户对页面“加载速度”的最初印象。

  • LCP (Largest Contentful Paint): 最大内容绘制,标记了视口中最大的可见元素完成渲染的时间点。这个元素通常是页面上的主要内容,因此 LCP 代表了用户何时看到页面的主要内容。

  • FID (First Input Delay): 首次输入延迟,测量用户首次与页面交互(例如点击链接、按钮或使用自定义 JavaScript 控件)到浏览器响应交互之间的时间。它反映了页面的交互响应速度,是用户对页面“响应速度”的最初印象。

以下表格概括了这些指标及其重要性:

指标 含义 重要性
FCP 首次内容绘制,首次渲染任何内容的时间 用户对页面加载速度的最初印象
LCP 最大内容绘制,最大元素完成渲染的时间 用户何时看到页面的主要内容,与用户的感知加载速度密切相关
FID 首次输入延迟,首次交互到响应的时间 用户对页面交互响应速度的最初印象,影响用户体验

2. 如何收集这些指标?

主要有两种方式收集这些指标:

  • Navigation Timing API 和 PerformanceObserver API: 这是浏览器提供的标准 API,也是最准确的方式。
  • Google Analytics 和其他第三方分析工具: 这些工具通常基于 Navigation Timing API 和 PerformanceObserver API 构建,并提供更高级的分析和报告功能。

我们重点介绍如何使用 Navigation Timing API 和 PerformanceObserver API 来收集这些指标。

2.1 使用 PerformanceObserver API

PerformanceObserver API 允许我们监听浏览器的性能事件。我们可以使用它来监听 paintfirst-input 事件,从而获取 FCP、LCP 和 FID 的数据。

2.1.1 获取 FCP

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    console.log('FCP candidate:', entry.startTime, entry.name);
  }
}).observe({ type: 'paint', buffered: true });

这段代码创建了一个 PerformanceObserver,监听 paint 类型的事件。当浏览器触发 paint 事件时(例如 FCP),entryList 中会包含相应的 PerformanceEntry 对象。我们可以从 entry.startTime 属性中获取事件发生的时间戳。entry.name 会是 "first-contentful-paint"

2.1.2 获取 LCP

new PerformanceObserver((entryList) => {
  let lastEntry;
  for (const entry of entryList.getEntries()) {
    lastEntry = entry;
  }
  if(lastEntry){
    console.log('LCP candidate:', lastEntry.startTime, lastEntry.url, lastEntry.element);
  }

}).observe({ type: 'largest-contentful-paint', buffered: true });

类似地,这段代码监听 largest-contentful-paint 类型的事件。由于 LCP 可能在页面加载过程中多次发生(例如,当页面上的大型图片加载完成时),我们需要记录最后一个 entry 作为最终的 LCP 值。lastEntry.startTime 是 LCP 的时间戳,lastEntry.url 是加载该元素的 URL,lastEntry.element 是该元素本身。

2.1.3 获取 FID

FID 的获取稍微复杂一些,因为它需要用户与页面进行交互。

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    console.log('FID candidate:', entry.startTime, entry.duration);
  }
}).observe({ type: 'first-input', buffered: true });

这段代码监听 first-input 类型的事件。entry.startTime 是用户首次与页面交互的时间戳,entry.duration 是浏览器响应交互所需的时间,也就是 FID。

注意: FID 需要真实的用户交互才能触发。如果在没有用户交互的情况下运行这段代码,它不会输出任何结果。另外,FID 只能在真实用户访问的场景下才能获取,因此本地开发环境下通常无法准确测量 FID。可以使用 Total Blocking Time (TBT) 作为 FID 的代理指标在开发环境下进行优化。

2.2 兼容性处理

PerformanceObserver API 的兼容性良好,但对于一些老旧的浏览器,可能需要进行 polyfill 或降级处理。

if ('PerformanceObserver' in window) {
  // 使用 PerformanceObserver API
} else {
  // 使用其他方式收集指标,例如 Navigation Timing API 或第三方库
  console.warn("PerformanceObserver is not supported in this browser.");
}

2.3 发送数据到后端

收集到这些指标后,我们需要将它们发送到后端服务器进行存储和分析。可以使用 fetchXMLHttpRequest API 来发送数据。

function sendData(data) {
  fetch('/api/performance', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(data)
  }).then(response => {
    if (!response.ok) {
      console.error('Failed to send performance data:', response.status);
    }
  }).catch(error => {
    console.error('Error sending performance data:', error);
  });
}

// 在收集到 FCP、LCP 和 FID 后,调用 sendData 函数
const performanceData = {
  fcp: fcpValue,
  lcp: lcpValue,
  fid: fidValue
};

sendData(performanceData);

这段代码定义了一个 sendData 函数,它将包含 FCP、LCP 和 FID 数据的 JSON 对象发送到 /api/performance 接口。

3. 如何分析这些指标?

收集到数据后,我们需要进行分析,找出性能瓶颈,并制定优化方案。

3.1 数据可视化

首先,我们需要将数据可视化,以便更直观地了解性能状况。可以使用各种图表类型,例如:

  • 折线图: 显示指标随时间的变化趋势。
  • 柱状图: 比较不同页面或不同用户的指标。
  • 分布图: 显示指标的分布情况。

例如,可以使用折线图来跟踪 FCP、LCP 和 FID 随时间的变化,从而了解优化措施的效果。

3.2 阈值设定

为了更好地评估性能,我们需要设定合理的阈值。Google 建议的 FCP、LCP 和 FID 阈值如下:

指标 良好 需要改进 较差
FCP <= 1.8 秒 1.8-3 秒 > 3 秒
LCP <= 2.5 秒 2.5-4 秒 > 4 秒
FID <= 100 毫秒 100-300 毫秒 > 300 毫秒

根据这些阈值,我们可以将用户体验划分为不同的等级,并针对不同等级的用户采取不同的优化策略。

3.3 寻找性能瓶颈

通过分析数据,我们可以找出性能瓶颈,例如:

  • FCP 过高: 可能是由于首屏资源加载缓慢、渲染阻塞或 JavaScript 执行时间过长。
  • LCP 过高: 可能是由于大型图片或视频加载缓慢、服务器响应时间过长或 CSS 阻塞渲染。
  • FID 过高: 可能是由于 JavaScript 执行时间过长、主线程繁忙或事件监听器阻塞。

3.4 制定优化方案

针对不同的性能瓶颈,我们可以制定相应的优化方案,例如:

  • 优化 FCP:
    • 减少首屏资源大小,例如使用图片压缩、代码分割和懒加载。
    • 优化关键渲染路径,例如使用 CSS inlining 和 JavaScript deferring。
    • 使用 CDN 加速资源加载。
  • 优化 LCP:
    • 优化 LCP 元素,例如使用响应式图片、优化视频编码和使用占位符。
    • 预加载 LCP 元素。
    • 优化服务器响应时间。
  • 优化 FID:
    • 减少 JavaScript 执行时间,例如使用代码优化、Tree Shaking 和 Web Workers。
    • 避免长时间运行的任务阻塞主线程。
    • 优化事件监听器。

4. 代码示例:整合收集和发送

下面是一个完整的代码示例,展示了如何使用 PerformanceObserver API 收集 FCP、LCP 和 FID,并将数据发送到后端服务器。

<!DOCTYPE html>
<html>
<head>
  <title>Performance Monitoring</title>
</head>
<body>
  <h1>Hello, World!</h1>
  <img src="large-image.jpg" alt="Large Image">
  <button id="myButton">Click Me</button>

  <script>
    function sendData(data) {
      fetch('/api/performance', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(data)
      }).then(response => {
        if (!response.ok) {
          console.error('Failed to send performance data:', response.status);
        }
      }).catch(error => {
        console.error('Error sending performance data:', error);
      });
    }

    let fcpValue, lcpValue, fidValue;

    // FCP
    new PerformanceObserver((entryList) => {
      for (const entry of entryList.getEntries()) {
        console.log('FCP candidate:', entry.startTime, entry.name);
        fcpValue = entry.startTime;
        // 确保只记录第一个 FCP
        observer.disconnect(); // Disconnect the observer after the first FCP
      }
    }).observe({ type: 'paint', buffered: true });

    // LCP
    let lcpObserver = new PerformanceObserver((entryList) => {
      let lastEntry;
      for (const entry of entryList.getEntries()) {
        lastEntry = entry;
      }
      if(lastEntry){
        console.log('LCP candidate:', lastEntry.startTime, lastEntry.url, lastEntry.element);
        lcpValue = lastEntry.startTime;
      }
    });

    lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true });

    // FID
    let fidObserver = new PerformanceObserver((entryList) => {
      for (const entry of entryList.getEntries()) {
        console.log('FID candidate:', entry.startTime, entry.duration);
        fidValue = entry.duration; // FID is the duration
      }

      //发送数据,只发送一次,因为 FID 是第一次输入延迟
      const performanceData = {
        fcp: fcpValue,
        lcp: lcpValue,
        fid: fidValue
      };
      sendData(performanceData);
      fidObserver.disconnect(); // Disconnect the observer after the first FID
    });

    fidObserver.observe({ type: 'first-input', buffered: true });

    // 模拟交互
    document.getElementById('myButton').addEventListener('click', () => {
      console.log('Button clicked!');
      // 模拟一些耗时操作
      let sum = 0;
      for (let i = 0; i < 10000000; i++) {
        sum += i;
      }
      console.log('Sum:', sum);
    });
  </script>
</body>
</html>

重要提示:

  • 这个示例代码仅用于演示目的。在实际项目中,需要根据具体情况进行修改和优化。
  • 需要确保后端服务器能够接收和处理性能数据。
  • 应该在真实用户访问的场景下进行测试,以获得更准确的性能数据。
  • 为避免重复发送FCP, LCP, FID, 监听器收集到数值以后,需要断开连接。
  • 可以考虑使用第三方库来简化性能监控的流程,例如 web-vitals

5. 其他注意事项

  • 采样率: 为了减少对性能的影响,可以设置采样率,只收集一部分用户的性能数据。
  • 数据清洗: 在分析数据之前,需要进行数据清洗,去除异常值和错误数据。
  • A/B 测试: 在进行性能优化时,可以使用 A/B 测试来验证优化效果。
  • 持续监控: 性能监控是一个持续的过程,需要定期收集和分析数据,并根据实际情况进行调整。

监控关键指标,优化用户体验

通过收集和分析 FCP、LCP 和 FID 等核心性能指标,我们可以更好地了解用户的感知性能,找出性能瓶颈,并制定优化方案,从而提升用户体验。记住,性能优化是一个持续的过程,需要定期监控和分析数据,并根据实际情况进行调整。 希望今天的分享对大家有所帮助。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注