前端性能指标详解:FP, FMP, LCP, 与 CLS

更新日期: 2025-08-23阅读: 38标签: 性能作者: 性能

今天,我们来深入探讨前端性能指标,重点关注 FP (First Paint - 首次绘制)、FMP (First Meaningful Paint - 首次有意义绘制)、LCP (Largest Contentful Paint - 最大内容绘制) 和 CLS (Cumulative Layout Shift - 累积布局偏移)。这些指标对于衡量网页加载体验至关重要,它们直接决定了用户是觉得你的网站“快”还是“稳定”。

我们将逐步解析如何测量、分析和应用这些指标。我们的目标是提供实用、无冗余的技术细节!

为什么要在乎性能指标?

前端性能直接影响用户体验。加载缓慢或内容跳跃的页面会赶走用户。谷歌的 核心网页指标 (Core Web Vitals) 将 LCP、CLS 和 FID (First Input Delay - 首次输入延迟,本文不深入探讨) 作为关键指标,它们甚至会影响搜索引擎排名。

虽然 FP 和 FMP 已不是官方核心指标,但它们在分析渲染过程中仍然至关重要。让我们从 FP 开始,逐一揭开这些指标的神秘面纱。

FP (First Paint - 首次绘制): 页面开始呈现内容的时刻

FP (First Paint) 标志着浏览器在屏幕上绘制第一个像素的时刻。这个像素可以是背景颜色、边框或任何视觉元素——不一定是用户关心的“主要内容”。FP 是一个基础指标,它表明页面从白屏到开始显示内容花费了多长时间。

如何测量 FP?

FP 是第一个渲染里程碑,通常通过 Performance api 来捕获。它发生在浏览器解析完 domcssOM 并开始绘制像素时。以下是测量方法:

// 监听页面加载完成事件
window.addEventListener("load", () => {
// 使用 Performance API 获取所有“paint”类型的性能条目
const performanceEntries = window.performance.getEntriesByType("paint");

// 找到名为 'first-paint' 的条目
const fpEntry = performanceEntries.find((entry) => entry.name === "first-paint");

if (fpEntry) {
    // fpEntry.startTime 是从页面导航开始到首次绘制发生的时间(毫秒)
    console.log(`首次绘制 (FP): ${fpEntry.startTime.toFixed(2)}ms`);
  } else {
    console.log("浏览器不支持 FP 或无法获取");
  }
});

这段代码使用 performance.getEntriesByType('paint') 来获取与绘制相关的性能条目,找到 first-paint 条目并记录其 startTime (以毫秒为单位)。大多数现代浏览器(Chrome、Edge、Firefox)都支持此 API。

FP 代表什么?

FP 标志着渲染的开始,表明浏览器已经开始工作。例如,背景颜色的变化或加载指示器的出现都会触发 FP。它通常发生在:

  1. 浏览器解析 html <head> 并加载了 CSS。
  2. 浏览器开始渲染第一个 DOM 元素(例如 <body> 的背景)。

真实世界的例子

思考一个简单的页面:

<!DOCTYPE html>
<html>
  <head>
    <title>我的页面</title>
    <link
      rel="stylesheet"
      href="styles.css"
    />
  </head>
  <body>
    <h1>Hello, World!</h1>
  </body>
</html>
/* styles.css */
body {
  background-color: #f0f0f0; /* 设置背景色 */
}
h1 {
  color: navy;
}

当浏览器加载 styles.css 并应用 body 的背景色时,屏幕从白色变为 #f0f0f0,这便触发了 FP。此时 <h1> 文本可能尚未出现,因为 DOM 和 CSSOM 可能仍在构建中。

FP 的局限性

FP 只追踪第一个像素的绘制,而不管内容是否有意义。例如,即使用户想看的文章或图片还未加载,一个背景色的变化也会触发 FP。这正是 FMP 和 LCP 发挥作用的地方。

FMP (First Meaningful Paint - 首次有意义绘制): 页面变得有意义的时刻

注意: FMP 是一个已被 Lighthouse 废弃的指标,因为它定义模糊且难以标准化。现在官方推荐使用 LCP。但了解 FMP 有助于理解性能演进历史。

FMP (First Meaningful Paint) 捕获页面主要内容(如文章标题、主图或导航栏)首次渲染的时刻。它不是一个标准化的浏览器指标,定义可能很模糊,但像 Lighthouse 这样的工具曾用它来分析加载体验。

如何测量 FMP?

FMP 无法通过浏览器 API 直接获得,因为“有意义”的内容因页面而异。Lighthouse 通过分析渲染时间线,寻找“视觉上显著”的元素(如大块文本或图片)出现的时间点来估算 FMP。

以下是如何在 Node.js 中使用 Lighthouse 测量 FMP:

// 引入 lighthouse 和 chrome-launcher 库
const lighthouse = require("lighthouse");
const chromeLauncher = require("chrome-launcher");

asyncfunction runLighthouse(url) {
// 启动一个无头 Chrome 实例
const chrome = await chromeLauncher.launch({ chromeFlags: ["--headless"] });
const options = {
    output: "json", // 输出格式为 JSON
    onlyCategories: ["performance"], // 只运行性能审计
  };

// 运行 Lighthouse 分析
const runnerResult = await lighthouse(url, options, chrome);

// 从报告中获取 FMP 的估算值(毫秒)
console.log("首次有意义绘制 (FMP):", runnerResult.lhr.audits["first-meaningful-paint"].numericValue, "ms");

// 关闭 Chrome 实例
await chrome.kill();
}

// 运行分析
runLighthouse("http://localhost:3000");

首先,需要安装依赖:

npm install lighthouse chrome-launcher

这段代码运行 Lighthouse,分析页面,并返回估算的 FMP 时间。Lighthouse 通过观察 DOM 变化和渲染事件来推断 FMP。

FMP 的挑战

FMP 的定义因页面而异,Lighthouse 的估算也未必总是准确。例如:

  • **单页应用 (SPA)**:使用 react/vue 构建的应用通过 JavaScript 渲染内容,这会将 FMP 推迟到 JS 执行之后。
  • 图片密集型页面:FMP 可能与图片加载绑定,而 Lighthouse 可能会错误判断。

这促使我们转向 LCP,这是 Google 提出的官方指标,旨在解决 FMP 的模糊性问题。

LCP (Largest Contentful Paint - 最大内容绘制): 最大内容块渲染的时刻

LCP (Largest Contentful Paint) 是核心网页指标之一,用于测量页面上最大的内容元素(如图片、视频或文本块)完全渲染出来所需的时间。它比 FMP 更精确,因为它只关注视口内最大的可见元素。

如何测量 LCP?

LCP 可以通过 Performance API 的 LargestContentfulPaint 条目捕获。浏览器会持续追踪视口中最大的内容元素,并记录其渲染时间。

// 创建一个 PerformanceObserver 实例来监听性能事件
const observer = new PerformanceObserver((entryList) => {
// getEntries() 返回一个性能条目数组
const entries = entryList.getEntries();
for (const entry of entries) {
    // entry.startTime 是 LCP 时间
    console.log(`最大内容绘制 (LCP): ${entry.startTime.toFixed(2)}ms`);
    // entry.element 是触发 LCP 的 DOM 元素
    console.log("LCP 元素:", entry.element);
  }
});

// 开始监听 'largest-contentful-paint' 类型的事件
// buffered: true 确保在观察者创建之前发生的事件也能被捕获
observer.observe({ type: "largest-contentful-paint", buffered: true });

你也可以在 Chrome DevTools 的 Performance 面板中查看 LCP。打开 DevTools (F12),转到 "Performance" 标签页,录制页面加载过程,LCP 会在时间线上被明确标记出来。

LCP 的定义

LCP 追踪的元素包括:

  • <img> 元素 (包括 <picture> 内的 <img>)。
  • <video> 元素的封面图片(poster image)。
  • 具有 background-image 的元素 (通过 url() 加载)。
  • 文本块(如 <p>, <h1>, <h2>)。
  • 包含文本或图片的容器,如 <p>。

浏览器会:

  1. 追踪视口中最大的内容元素。
  2. 记录它完全渲染的时间(例如,图片加载完成或文本绘制完成)。
  3. 如果稍后出现一个更大的元素(例如,滚动后加载了一张新图片),LCP 会被更新。

代码示例

这是一个突出 LCP 的页面:

<!DOCTYPE html>
<html>
<head>
    <title>LCP 演示</title>
    <style>
      .hero {
        width: 100%;
        height: 400px;
        /* 一个大的背景图通常是 LCP 元素 */
        background: url("hero.jpg") no-repeat center/cover;
      }
      h1 {
        font-size: 48px;
      }
    </style>
</head>
<body>
    <p class="hero"></p>
    <h1>欢迎来到我的网站</h1>
    <p>一些内容...</p>
</body>
</html>

假设 hero.jpg 是一张大图(例如 1920x1080),渲染顺序可能是:

  1. FP: 页面背景色(默认为白色)绘制。
  2. FMP (估算): <h1> 标题渲染(可能是主要内容)。
  3. LCP: .hero 的背景图完全加载并绘制(成为视口中最大的元素)。

LCP 优化注意事项

  • 图片加载:如果 LCP 元素是图片,使用现代格式(如 WebP, AVIF)并压缩文件大小。
  • 字体加载:对于基于文本的 LCP,缓慢的字体文件(WOFF2)会延迟渲染。
  • 动态内容:在 SPA 中,LCP 可能在 React/Vue 渲染主组件后才发生。

CLS (Cumulative Layout Shift - 累积布局偏移): 页面有多稳定?

CLS (Cumulative Layout Shift) 衡量页面内容在加载过程中“跳动”或“偏移”的程度。用户最讨厌的就是,正要点击一个按钮,页面突然移动,导致误点。CLS 是核心网页指标之一,旨在确保页面稳定性。

如何测量 CLS?

CLS 是通过 LayoutShift 性能条目计算的。浏览器会追踪所有非用户交互触发的布局变化,并计算一个“偏移分数”。

let clsScore = 0;
// 创建 PerformanceObserver 来监听布局偏移事件
const observer = new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
    // 忽略由用户输入(如点击、滚动)在 500ms 内触发的布局偏移
    if (!entry.hadRecentInput) {
      // entry.value 是单次布局偏移的分数
      clsScore += entry.value;
      console.log(`单次布局偏移分数: ${entry.value.toFixed(4)}`);
    }
  }
// 累加所有分数得到最终的 CLS
console.log(`累积布局偏移 (CLS): ${clsScore.toFixed(4)}`);
});

// 开始监听 'layout-shift' 类型的事件
observer.observe({ type: "layout-shift", buffered: true });

根据 Google 的标准,CLS 分数低于 0.1 是理想的。你可以使用 Chrome DevTools 的 Performance 面板来可视化 CLS,发生偏移的元素会被高亮显示。

CLS 的计算方式

CLS 是所有单个布局偏移分数的总和,每个分数按以下方式计算:

偏移分数 = 影响因子 (Impact Fraction) × 距离因子 (Distance Fraction)

  • 影响因子:受偏移影响的视口区域比例。例如,一个元素从顶部移动到底部,可能会影响 50% 的视口。
  • 距离因子:元素移动的距离相对于视口高度(或宽度)的比例。例如,移动了视口高度的 10%,则距离因子为 0.1。

代码示例

这是一个会导致 CLS 的页面:

<!DOCTYPE html>
<html>
<head>
    <title>CLS 演示</title>
    <style>
      .banner {
        width: 100%;
        height: 100px;
        background: lightblue;
      }
      .content {
        margin-top: 10px;
      }
    </style>
</head>
<body>
    <p class="content">
      <h1>主要内容</h1>
      <p>这部分内容可能会移动...</p>
    </p>
    <script>
      // 模拟异步加载广告
      setTimeout(() => {
        const banner = document.createElement("p");
        banner.className = "banner";
        // 将广告横幅插入到 body 的最前面,导致下面的内容移动
        document.body.insertBefore(banner, document.body.firstChild);
      }, 2000);
    </script>
</body>
</html>

这个页面:

  1. 首先渲染 <h1> 和 <p>。
  2. 2 秒后,JavaScript 插入一个 100px 高的 .banner,导致 .content 向下移动。

修复 CLS 的常见方法

  • 为图片设置尺寸:给 <img> 标签添加 width 和 height 属性,以便浏览器提前预留空间。

    <img
      src="image.jpg"
      width="800"
      height="600"
      alt="示例图片"
    />
  • 使用 CSS 占位符:为即将加载的动态内容预留空间。

    .banner-placeholder {
      width: 100%;
      min-height: 100px; /* 预留空间 */
    }
  • 避免在现有内容上方插入新内容

综合分析:一个真实场景

让我们分析一个包含 FP、FMP、LCP 和 CLS 的真实页面:

<!DOCTYPE html>
<html>
<head>
    <title>新闻网站</title>
    <link
      rel="stylesheet"
      href="styles.css"
    />
    <!-- 加载外部字体 -->
    <link
      href="https://fonts.googleapis.com/css2?family=Roboto"
      rel="stylesheet"
    />
</head>
<body>
    <header>
      <h1>新闻网站</h1>
    </header>
    <main>
      <!-- LCP 候选元素 -->
      <img
        src="hero.jpg"
        alt="主图"
      />
      <h2>头条新闻</h2>
      <p>突发新闻内容...</p>
    </main>
    <!-- 异步加载的 JS 可能导致 CLS -->
    <script
      src="ads.js"
      async
    ></script>
</body>
</html>
/* styles.css */
body {
background-color: #f0f0f0;
}
header {
background: navy;
color: white;
padding: 10px;
}
/* 图片未设置高度,可能导致 CLS */
img {
width: 100%;
height: auto;
}
main {
margin: 20px;
}
h2,
p {
font-family: "Roboto", sans-serif;
}
// ads.js
// 模拟一个广告脚本,在 3 秒后插入一个横幅
setTimeout(() => {
  const ad = document.createElement("p");
  ad.style.height = "100px";
  ad.style.background = "lightgreen";
  document.body.insertBefore(ad, document.body.firstChild);
}, 3000);

性能分析与优化:

  1. FP: 很快,因为只是 body 的背景色(约 200ms)。
  2. FMP (估算): <h2> 和 <p> 的渲染(约 500ms),受字体加载影响。
    • 优化: 使用 font-display: swap; 让系统字体先显示。
  3. LCP: <img> 完全加载(约 1000ms+),取决于图片大小和网络。
    • 优化: 压缩图片,使用 WebP 格式,并为视口外的图片添加 loading="lazy"。
  4. CLS:
    • 优化: 为广告位设置一个 min-height: 100px 的占位容器。
    • 优化: 为 <img> 设置 width 和 height 属性,或使用 CSS 的 aspect-ratio 属性。
    • 原因1: 图片加载完成前没有尺寸,导致 height: auto 生效后推开下方内容。
    • 原因2: 广告插入导致 <main> 下移。

单页应用 (SPA) 场景:React 应用性能

使用 React/Vue 等框架的 SPA 由于其动态渲染机制,使性能指标分析变得复杂。

// src/App.js
import React, { useEffect, useState } from"react";
import"./App.css";

function App() {
const [adVisible, setAdVisible] = useState(false);

  useEffect(() => {
    // 模拟 3 秒后显示广告
    const timer = setTimeout(() => {
      setAdVisible(true);
    }, 3000);
    return() => clearTimeout(timer);
  }, []);

return (
    <p>
      {/* 广告位,会导致布局偏移 */}
      {adVisible && <p className="ad-banner">广告</p>}

      <header>
        <h1>React 新闻</h1>
      </header>
      <main>
        <img
          src="hero.jpg"
          alt="主图"
          style={{ width: "100%" }}
        />
        <h2>头条新闻</h2>
        <p>突发新闻内容...</p>
      </main>
    </p>
  );
}
exportdefault App;

SPA 性能挑战与改进

  • JS 阻塞:大型 JS 包会因为解析和执行而延迟 FMP 和 LCP。

    • 改进: 使用代码分割 (React.lazy 和 Suspense)。
  • 动态渲染: 通过 useEffect 或异步请求加载的内容会延迟 LCP。

  • CLS 风险: 动态组件(如广告、评论)会增加布局偏移风险。

    // 使用占位符修复 CLS
    .ad-placeholder {
      min-height: 100px;
    }

    {/* 在广告加载前显示占位符 */}
    {adVisible ? <p className="ad-banner">广告</p> : <p className="ad-placeholder"></p>}
    • 改进: 为动态内容预留空间。

工具支持

  • Chrome DevToolsPerformance 面板可以直观地显示 FP、LCP 和 CLS。

  • Lighthouse: 运行全面的性能审计,提供 FMP、LCP 等指标的得分和建议。

  • web-vitals 库: Google 官方的轻量级库,用于在真实用户环境中监控 LCP 和 CLS。

    npm install web-vitals
    import { getLCP, getCLS } from "web-vitals";

    // 监控 LCP
    getLCP((metric) => console.log("LCP:", metric.value.toFixed(2), "ms"));

    // 监控 CLS
    getCLS((metric) => console.log("CLS:", metric.value.toFixed(4)));

结论

FP、FMP、LCP 和 CLS 是衡量前端性能的关键指标:

  • FP (首次绘制): 标志着渲染的开始。
  • FMP (首次有意义绘制): 估算有用内容出现的时间点(已废弃)。
  • LCP (最大内容绘制): 精确测量主要元素加载速度,代表加载性能
  • CLS (累积布局偏移): 衡量页面稳定性,代表视觉稳定性

通过使用 Performance API、Lighthouse 和 web-vitals 库,您可以准确地测量这些指标。本文中的代码示例演示了如何进行监控和分析,并通过真实世界的案例(静态页面、SPA、复杂页面)阐明了每个指标的触发条件和影响。

现在,就动手试试这些脚本,打开 DevTools,享受性能分析的乐趣吧!

来自公众号:OTT前端技术

本文内容仅供个人学习/研究/参考使用,不构成任何决策建议或专业指导。分享/转载时请标明原文来源,同时请勿将内容用于商业售卖、虚假宣传等非学习用途哦~感谢您的理解与支持!

链接: https://fly63.com/article/detial/12891

提高js加载速度,实现js无阻塞加载方式,高性能的加载执行JavaScript

为解决JS加载速度慢,采用js的延时加载,和动态加载。由于js的堵塞特性,当浏览器在加载javascript代码时,不能同时做其他任何事情,如果javascript执行时间越久,浏览器等待响应的时间就越久。

如何提高CSS性能?CSS优化、提高性能提升总汇

如何提高CSS性能,根据页面的加载性能和CSS代码性能,主要表现为: 加载性能 (主要是从减少文件体积,减少阻塞加载,提高并发方面入手),选择器性能,渲染性能,可维护性。

前端性能优化_css加载会造成哪些阻塞现象?

css的加载是不会阻塞DOM的解析,但是会阻塞DOM的渲染,会阻塞link后面js语句的执行。这是由于浏览器为了防止html页面的重复渲染而降低性能,所以浏览器只会在加载的时候去解析dom树,然后等在css加载完成之后才进行dom的渲染以及执行后面的js语句。

2018 前端性能检查表

性能十分重要。然而,我们真的知道性能瓶颈具体在哪儿吗?是执行复杂的 JavaScript,下载缓慢的 Web 字体,巨大的图片,还是卡顿的渲染?研究摇树(Tree Shaking),作用域提升(Scope Hoisting)

高性能Javascript总结

Js高性能总结:加载和运行、数据访问、DOM编程、算法和流程控制、响应接口、Ajax 异步JavaScript和XML、编程实践...

优化网站性能规则_前端性能优化策略【网络加载、页面渲染】

前端网站性能优化规则:网络加载类、页面渲染类。包括:减少 HTTP 资源请求次数、减小 HTTP 请求大小、避免页面中空的 href 和 src、合理设置 Etag 和 Last-Modified、使用可缓存的 AJAX、减少 DOM 元素数量和深度等

前端性能的本质是什么?

性能一直以来是前端开发中非常重要的话题。随着前端能做的事情越来越多,浏览器能力被无限放大和利用:从 web 游戏到复杂单页面应用,从 NodeJS 服务到 web VR/AR 和数据可视化,前端工程师总是在突破极限

BigPipe_高性能流水线页面技术

BigPipe是一个重新设计的基础动态网页服务体系。大体思路是,分解网页成叫做Pagelets的小块,然后通过Web服务器和浏览器建立管道并管理他们在不同阶段的运行。这是类似于大多数现代微处理器的流水线执行过程:多重指令管线通过不同的处理器执行单元,以达到性能的最佳。

用CSS开启硬件加速来提高网站性能

你知道我们可以在浏览器中用css开启硬件加速,使GPU (Graphics Processing Unit) 发挥功能,从而提升性能吗?现在大多数电脑的显卡都支持硬件加速。鉴于此,我们可以发挥GPU的力量,从而使我们的网站或应用表现的更为流畅。

原生js实现懒加载并节流

像淘宝网站等,页面中有着大量图片,一次性全部加载这些图片会使浏览器发送大量请求和造成浪费。采用懒加载技术,即用户浏览到哪儿,就加载该处的图片。这样节省网络资源、提升用户体验、减少服务器压力。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!