FP、FCP、LCP、FMP、DOM Ready等的监控

249 阅读4分钟

测页面的 FP(First Paint)、FCP(First Contentful Paint)、LCP(Largest Contentful Paint)、DOM Ready 这些关键性能指标,通常通过 Web API 和性能监控工具来实现。

1. FP(First Paint)和 FCP(First Contentful Paint)

FP 和 FCP 是页面加载过程中关键的初始渲染指标,通常使用 PerformanceObserver 监控这些时间点。

JavaScript 代码示例:

javascript
复制代码
const observer = new PerformanceObserver((entryList) => {
  const entries = entryList.getEntries();
  entries.forEach((entry) => {
    if (entry.name === 'first-paint') {
      console.log('FP:', entry.startTime);
    } else if (entry.name === 'first-contentful-paint') {
      console.log('FCP:', entry.startTime);
    }
  });
});

observer.observe({ type: 'paint', buffered: true });
  • first-paint: 表示浏览器首次将任何内容绘制到屏幕的时间。
  • first-contentful-paint: 表示浏览器首次绘制页面内容(如文本、图像)的时间。

2. LCP(Largest Contentful Paint)

LCP 测量的是页面中最大的可见元素(例如大图、块状内容)渲染的时间点。

JavaScript 代码示例:

javascript
复制代码
const observer = new PerformanceObserver((entryList) => {
  const entries = entryList.getEntries();
  entries.forEach((entry) => {
    console.log('LCP:', entry.startTime);
  });
});

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

LCP 是用户体验的关键性能指标之一,通常代表页面在用户视角下完成渲染的时间。

3. DOM Ready

DOM Ready 表示页面的 HTML 已经完全加载并解析,但外部资源(如图片、样式表)可能尚未加载完成。可以通过监听 DOMContentLoaded 事件来获取该时间点。

JavaScript 代码示例:

javascript
复制代码
document.addEventListener('DOMContentLoaded', () => {
  const domReadyTime = performance.now();
  console.log('DOM Ready:', domReadyTime);
});

综合方案:使用 Performance API 全面监控页面性能

通过 window.performance.timingPerformanceObserver 可以获取更多关键指标。还可以结合 sendBeacon 或其他异步数据上报方式,将这些性能指标发送到后端,便于后续的分析和优化。

这些性能数据的采集,帮助开发者分析并优化页面加载体验。

requestAnimationFrame 可以用于检测页面渲染的时机,因此可以间接帮助我们检测 FMP(首次有意义渲染)。虽然 requestAnimationFrame 不能直接获取 FMP,但它能让我们准确地监控页面中重要元素的渲染时间,这对于检测 FMP 非常有帮助。

通过 requestAnimationFrame 检测 FMP 的实现思路

  1. 定义关键内容:页面上哪些内容是"有意义的"(如主要文字、图片等)。
  2. 监听这些内容的渲染:通过 requestAnimationFrame 轮询页面的渲染状态,当关键内容出现在页面上时,我们记录渲染的时间。

示例代码

假设我们将页面中的 #main-content 作为主要内容,想检测该元素首次被渲染到屏幕的时刻:

javascript
复制代码
function detectFMP() {
  const mainContent = document.querySelector('#main-content');
  if (!mainContent) {
    console.error('未找到关键元素');
    return;
  }

  let fmpDetected = false;

  // 递归调用 requestAnimationFrame 来不断检测内容是否渲染
  function checkForFMP() {
    // 检查是否已渲染(通过元素的尺寸和可见性)
    const boundingRect = mainContent.getBoundingClientRect();
    if (boundingRect.width > 0 && boundingRect.height > 0 && !fmpDetected) {
      const fmpTime = performance.now();
      console.log('首次有意义渲染(FMP)时间:', fmpTime);
      fmpDetected = true;  // 确保只记录一次
    } else {
      // 如果还没渲染,继续下一帧的检测
      requestAnimationFrame(checkForFMP);
    }
  }

  // 开始检测
  requestAnimationFrame(checkForFMP);
}

// 当 DOM 内容加载完毕时,开始监控
window.addEventListener('DOMContentLoaded', detectFMP);

代码说明

  1. detectFMP 函数

    • 通过 document.querySelector 找到页面中关键的 #main-content 元素。
    • 使用 requestAnimationFrame 进行递归调用,每一帧都检查这个元素的渲染状态。
  2. requestAnimationFrame 的作用

    • requestAnimationFrame 是在浏览器每次重绘之前调用的函数,所以可以用于监控页面渲染的时机。
    • 通过持续检查 #main-content 的尺寸(getBoundingClientRect),判断该元素何时被渲染到屏幕上。
  3. FMP 的检测

    • #main-content 元素的宽度和高度都大于零,说明这个关键元素已经被渲染到页面上。
    • 记录当前的时间(使用 performance.now() 获取相对精确的时间),并将其作为 FMP 时间
  4. fmpDetected 标志

    • 为了确保 FMP 只被记录一次,使用一个标志位 fmpDetected,当检测到 FMP 后,将其设为 true,避免重复记录。

下面是一个简单的心跳包机制的实现示例,使用 JavaScript 和 WebSocket。在这个示例中,客户端会定期发送心跳信号到服务器,并处理服务器的响应。

1. 客户端代码(JavaScript)

javascript
复制代码
const socket = new WebSocket('ws://yourserver.com/socket');

let heartBeatInterval;
const heartBeatTimeout = 5000; // 5秒的心跳间隔
const heartBeatResponseTimeout = 3000; // 3秒的响应超时

function startHeartBeat() {
  heartBeatInterval = setInterval(() => {
    if (socket.readyState === WebSocket.OPEN) {
      socket.send(JSON.stringify({ type: 'heartbeat' }));
      console.log('发送心跳包');
      // 设置心跳响应超时
      setTimeout(() => {
        if (!responseReceived) {
          console.error('未收到心跳响应,可能连接已断开');
          socket.close();
        }
        responseReceived = false; // 重置响应标志
      }, heartBeatResponseTimeout);
    }
  }, heartBeatTimeout);
}

let responseReceived = false;
socket.onmessage = (event) => {
  const data = JSON.parse(event.data);
  if (data.type === 'heartbeatResponse') {
    console.log('收到心跳响应');
    responseReceived = true;
  }
};

socket.onopen = () => {
  console.log('连接已建立');
  startHeartBeat();
};

socket.onclose = () => {
  clearInterval(heartBeatInterval);
  console.log('连接已关闭');
};

socket.onerror = (error) => {
  console.error('WebSocket 错误:', error);
};