# 前端性能指标

  • FP(First Paint),首次绘制。

  • FCP(First Contentful Paint),首次有内容的绘制。

  • FMP(First Meaningful Paint),首次有意义的绘制。

  • TTFB(Time To First Byte),首字节时间。

  • TTI(Time To Interactive),可交互时间,推荐的响应时间是 100ms 以内,否则有延迟感。

  • TBT(Total Blocking Time),总阻塞时间。记录在 FCP 到 TTI 之间所有⻓任务的阻塞时间总和。

  • LCP(Largest Contentful Paint),最大内容绘制。LCP 度量了在⻚⾯加载过程中,最⼤的(通常是主要的)内容元素何时呈现在屏幕上。⽐如⻚⾯上的⼀张主图或者⼀段⽂字。LCP 提供了⽤户观看到⻚⾯主要内容的速度。

  • FID(First Input Delay),首次输入延迟。FID 度量⽤户⾸次交互(例如点击链接、按钮等)和浏览器开始处理此交互之间的时间。这个指标关注的是⽹⻚的交互反应速度。

  • CLS(Cumulative Layout Shift),累计布局偏移。CLS 度量视觉内容在加载过程中发⽣意外布局偏移的程度。比如使用按钮动态添加了某个元素,导致⻚面上其他位置的代码发生了偏移。这个指标关注的是⻚⾯的视觉稳定性。

快速获取各项性能指标

web-vitals (opens new window) 这个库可以让我们很方便的获取到一些性能指标。

tti-polyfill (opens new window) 这是一个可以获取 TTI 指标的库。

# 核心性能指标

  • LCP 代表⻚面的速度指标,LCP 能体现的东⻄更多一些。一是指标实时更新,数据更精确,二是代表着⻚面最大元素的渲染时间,最大元素的快速载入能让用户感觉性能还挺好。

  • FID 代表⻚面的交互体验指标,交互响应的快会让用户觉得网⻚流畅。

  • CLS 代表⻚面的稳定指标,尤其在手机上这个指标更为重要。因为手机屏幕挺小,CLS 值一大的话会让用户觉得⻚面体验做的很差。

# FCP

# 获取 FCP 时间

要获取⾸次内容绘制(First Contentful Paint, FCP)的时间,可以使⽤浏览器的性能 API,特别是 PerformanceObserver 接⼝。

if ("PerformanceObserver" in window) {
  let observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntriesByName("first-contentfulpaint")) {
      console.log("FCP time:", entry.startTime);
    }
  });
  observer.observe({ type: "paint", buffered: true });
}
1
2
3
4
5
6
7
8

# FCP 的指标分数段

FCP 的分数通常根据加载时间来划分,通常的分数段如下:

  • 优秀:FCP 时间低于 1 秒。

  • 良好:FCP 时间在 1-2.5 秒之间。

  • 需要改进:FCP 时间在 2.5-4 秒之间。

  • 较差:FCP 时间超过 4 秒。

# 如何提升 FCP

提升 FCP 的关键在于减少⽹⻚内容的加载时间,尤其是⽹⻚的初始渲染部分。

以下是⼀些优化 FCP 的策略:

1. 优化服务器响应时间

  • 使⽤更快的托管服务。

  • 优化后端逻辑和数据库查询。

2. 减少关键渲染路径⻓度

  • 识别和优化影响⾸次渲染的关键资源。

  • 使⽤异步或延迟加载阻塞 JavaScript 和 CSS。

3. 减少资源⼤⼩

  • 压缩 CSS、JavaScript 和 HTML。

  • 优化图像⼤⼩和格式。

4. 利⽤缓存策略

使⽤浏览器缓存来存储重复使⽤的资源。

5. 使⽤内容分发⽹络(CDN)

  • 通过 CDN 分发内容,减少地理位置对加载时间的影响。

  • CDN 还有另一个好处就是,能够减少 cookie 的携带。

6. 移除或优化重量级第三⽅脚本

分析和移除增加显著加载时间的第三⽅脚本。

7. 预加载关键资源

使⽤ <link rel="preload"> 预加载⾸屏关键资源。

8. 优化字体加载

避免使⽤过多的字体变体,使⽤ font-display: swap (opens new window) 属性来减少字体阻塞渲染。

# TTI

⾸次交互时间(TTI,Time to Interactive)是⼀个衡量⻚⾯可交互性的关键性能指标。它表示⻚⾯从开始加载到主要⼦资源已加载并且能够快速响应⽤户输⼊所需的时间。TTI 对于了解⽤户体验的质量⾮常重要。

# 获取 TTI 时间

获取 TTI 的⼀个常⽤⽅法是使⽤ Chrome 的 Lighthouse ⼯具,但也可以通过 Performance Observer API 在 JavaScript 中监控:

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    if (entry.entryType === "longtask") {
      console.log("Long Task detected:", entry);
      // TTI 可能被这个⻓任务推迟
    }
  }
}).observe({ entryTypes: ["longtask"] });
// 你也可以使⽤ Performance Timing API 来获取近似的 TTI 值
const tti = window.performance.timing.domInteractive;
1
2
3
4
5
6
7
8
9
10

这段代码将监控⻓任务,这些⻓任务通常会推迟 TTI。请注意,这只提供了近似的 TTI,⽽⾮精确的测量。

# TTI 的指标分数段

TTI 的标准分数段通常如下:

  • 好:⼩于等于 3.8 秒。

  • 需要改进:3.8 秒到 7.3 秒之间。

  • 差:超过 7.3 秒。

# 如何提升 TTI

1. 优化 JavaScript 加载和执⾏

  • 减少 JavaScript ⽂件⼤⼩。

  • 使⽤代码分割以异步加载⾮关键 JavaScript。

  • 避免⻓任务阻塞主线程。

2. 最⼩化关键请求深度

  • 优化关键渲染路径。

  • 减少关键资源数量。

3. 服务器端渲染(SSR)或静态⽣成

使⽤服务器端渲染或静态⽣成以减少客户端渲染的⼯作量。

4. 使⽤预加载和预连接技术

  • 使⽤ <link rel="preload"> 预加载关键资源。

  • 使⽤ <link rel="preconnect"> 提前建⽴必要的连接。

5. 优化字体加载

使⽤ font-display: swap 等策略优化字体的可⻅加载。

6. 优化图⽚和媒体资源

  • 对图⽚和视频进⾏压缩和格式优化。

  • 使⽤懒加载策略。

7. 避免⼤型 DOM 结构

简化⻚⾯的 DOM 结构。

8. 使⽤⾼效的 CSS

避免复杂的 CSS 选择器和布局重排。

# LCP

最大内容绘制(LCP,Largest Contentful Paint)是⼀个重要的 Web 性能指标,⽤于衡量从⻚⾯开始加载到⻚⾯上最⼤的⽂本块或图像元素被渲染的时间。理解和优化 LCP 对于改善⽤户体验⾄关重要。

# 获取 LCP 时间

可以使⽤ Performance Observer API 来监控和获取 LCP 数据:

// 创建⼀个性能观察者来观察最⼤内容绘制
let observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    // 处理每个 LCP 性能条⽬
    console.log("LCP candidate:", entry.startTime, entry);
  }
});
// 开始观察
observer.observe({ type: "largest-contentful-paint", buffered: true });
1
2
3
4
5
6
7
8
9

这段代码会监控 LCP 事件,并在每次测量到 LCP 时输出其信息。

# LCP 的指标分数段

LCP 的分数段通常如下:

  • 好:2.5 秒或更快。

  • 需要改进:2.5 秒到 4.0 秒之间。

  • 差:超过 4.0 秒。

performance

# 如何提升 LCP

1. 优化图像

  • 使⽤更⼩的、优化过的图像。

  • 使⽤现代格式,如 WebP。

  • 对于较⼤的图像,考虑懒加载。

2. 优化服务器/CDN

  • 使⽤ CDN 加速内容分发。

  • 优化服务器响应时间。

3. 优化 CSS 和 JavaScript

  • 减少关键渲染路径上的 CSS 和 JavaScript。

  • 异步加载⾮关键 JavaScript。

  • 内联关键 CSS。

4. 使⽤预加载

使⽤ <link rel="preload"> 预加载关键资源。

5. 优化 Web 字体

优化字体加载并考虑使⽤ font-display: swap。

6. 优化客户端渲染

如果使⽤客户端渲染框架,考虑服务器端渲染或静态⽣成。

7. 优化 DOM 结构

简化 DOM,减少深层嵌套。

# FID

⾸次输⼊延迟(FID, First Input Delay)是⼀个衡量⻚⾯交互性的重要性能指标,它记录了从⽤户⾸次与⻚⾯交互(例如点击链接、按钮等)到浏览器响应该交互的时间。这个指标重要地反映了⽤户体验的响应性。

# 获取 FID 时间

可以使⽤以下代码⽚段来监控和获取 FID 数据:

new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    if (entry.entryType === "first-input") {
      console.log(
        "First Input Delay:",
        entry.processingStart - entry.startTime
      );
    }
  }
}).observe({ type: "first-input", buffered: true });
1
2
3
4
5
6
7
8
9
10

这段代码创建了⼀个性能观察者,专⻔⽤于监控⾸次输⼊事件。

# FID 的指标分数段

FID 的分数段⼀般如下:

  • 好:⼩于 100 毫秒。

  • 需要改进:100-300 毫秒。

  • 差:超过 300 毫秒。

performance

# 如何提升 FID

1. 减少 JavaScript 执⾏时间

  • 减少⻓任务,尽量将⻓的 JavaScript 任务拆分成更⼩的任务。

  • 使⽤代码分割(Code Splitting)来减少初始加载的 JavaScript 量。

  • 延迟⾮关键 JavaScript 的加载,例如使⽤ defer 属性。

2. 优化⻚⾯加载

  • 使⽤预加载技术预加载关键资源。

  • 优化服务器响应时间和资源压缩。

3. 避免不必要的第三⽅脚本

减少第三⽅脚本的使⽤,或推迟其加载。

4. 使⽤ Web Workers

对于复杂的计算任务,考虑使⽤ Web Workers。JS 不仅可以使用 Web Workers 实现多线程,而且还支持线程锁 Atomics。

# React 和 Vue 对于提升 FID 的贡献

React 和 Vue 本身对于直接提升 FID 没有特定优势,但它们的架构和⽣态提供了优化的可能性:

  • 虚拟 DOM:React 和 Vue 使⽤虚拟 DOM 来优化 DOM 更新,减少实际的 DOM 操作成本,从⽽可以在⼀定程度上减少渲染阻塞时间。

  • 异步组件:React 和 Vue ⽀持异步组件和代码分割,使得开发者可以更灵活地控制资源加载和渲染时机。

  • ⽣态⼯具:React 和 Vue 的⽣态系统中有许多性能优化⼯具和库,如 react-lazy、vue-router 的懒加载等,有助于优化⻚⾯加载和交互性。

# CLS

⾸次内容绘制(CLS,Cumulative Layout Shift)是⽤来衡量⻚⾯可视化稳定性的重要性能指标。它量化了⻚⾯加载过程中意外布局变化的总和,⽬的是识别和减少⽤户体验中的视觉稳定性问题。

# 获取 CLS

可以通过以下 JavaScript 代码来监控 CLS:

let clsValue = 0;
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    if (!entry.hadRecentInput) {
      clsValue += entry.value;
    }
  }
  console.log("Current CLS value:", clsValue);
}).observe({ type: "layout-shift", buffered: true });
1
2
3
4
5
6
7
8
9

这段代码创建了⼀个性能观察者来监控布局变化,并计算 CLS 值。 hadRecentInput 字段⽤于检查布局偏移是否由⽤户输⼊引起,以避免计算⽤户主动引起的布局变化。

CLS 的计算方法,以下面这张图为例。

performance

文本移动了 25% 的屏幕高度距离(位移距离),位移前后影响了 75% 的屏幕高度面积(位移影响的面积),那么 CLS 为 0.25 * 0.75 = 0.1875。

CLS 推荐值为低于 0.1,越低说明⻚面跳来跳去的情况就越少,用户体验越好。毕竟很少有人喜欢阅读或者交互过程中网⻚突然动态插入 DOM 的情况,比如说插入广告.

# CLS 的指标分数段

CLS 分数通常按以下标准划分:

  • 好:CLS ≤ 0.1。

  • 需要改进:0.1 < CLS ≤ 0.25。

  • 差:CLS > 0.25。

performance

# 如何提升 CLS

1. 稳定尺⼨的媒体内容

  • 为图像和视频元素设置显式的宽度和⾼度,以防⽌加载时的布局变化。

  • 使⽤ CSS 的 aspect-ratio (opens new window) 属性为⽆尺⼨媒体保留空间。

2. 避免插⼊动态内容

尽量避免在⻚⾯主要内容上⽅动态添加内容,这会导致下⽅内容突然下移。

3. 优化字体加载

使⽤ font-display: optional 或 font-display: swap 属性减少字体加载对布局的影响。

4. 预留⼴告和嵌⼊框架空间

为⼴告和嵌⼊内容预留⾜够的空间,避免加载时导致内容移动。

5. 动画和过渡优化

在实现动画和过渡时使⽤ transform 属性,这种变化不会影响⻚⾯布局。

6. 使⽤容器包裹异步加载的内容

异步加载的内容(如评论区、推荐列表)应该在固定尺⼨的容器中加载,避免内容渲染导致的布局偏移。

# TTFB

⾸次字节时间(TTFB, Time to First Byte)是衡量服务器响应速度的⼀个重要性能指标,指的是从浏览器开始请求到接收到第⼀个字节的时间。TTFB 对于服务器性能和⽹络响应时间都⾮常敏感。

# 获取 TTFB 时间

可以通过浏览器的 Performance API 获取 TTFB:

window.onload = function() {
  var ttfb =
    window.performance.timing.responseStart -
    window.performance.timing.requestStart;
  console.log("TTFB: " + ttfb + "ms");
};
1
2
3
4
5
6

这段代码会在⻚⾯加载时计算并打印 TTFB 值。

# TTFB 的指标分数段

TTFB 的⼀般标准分为:

  • 好:⼩于 200 毫秒。

  • 需要改进:200-500 毫秒。

  • 差:⼤于 500 毫秒。

# 如何提升 TTFB

提升 TTFB 主要涉及到服务器性能、⽹络优化和应⽤程序优化。需要注意的是,TTFB 也受到⽤户的⽹络环境影响,因此在全球范围内提供⼀致的 TTFB 性能可能需要⼀些额外的⼯作,如使⽤全球 CDN。

1. 优化服务器性能

  • 使⽤更快的服务器或升级服务器硬件。

  • 优化服务器配置和代码,如数据库查询、服务器端脚本优化等。

2. 使⽤ CDN

使⽤内容分发⽹络(CDN)可以将内容缓存到离⽤户更近的地点,从⽽降低响应时间。

3. 减少⽹络延迟

  • 优化⽹络路径,确保服务器与⽤户之间的⽹络延迟最⼩。

  • 选择靠近⽤户的服务器位置。

4. 优化应⽤程序

  • 减少服务器端渲染所需的资源和时间。

  • 使⽤异步操作和后台处理来提⾼效率。

5. 数据库优化

  • 对数据库进⾏索引和查询优化。

  • 避免复杂的数据库操作和⼤量的数据库请求。

6. 减少 HTTP 请求

  • 减少⻚⾯加载所需的 HTTP 请求数量。

  • 合并资源⽂件,如 CSS 和 JavaScript。

7. 启⽤压缩

使⽤ GZIP 或 Brotli 等压缩技术来减⼩发送到客户端的数据量。

# TBT

总阻塞时间(TBT, Total Blocking Time)是⼀个重要的⽹⻚性能指标,⽤于量化⻚⾯加载过程中⽤户体验到的阻塞时间。TBT 衡量的是从⻚⾯开始加载到可以响应⽤户输⼊之间的时间,特别是那些由于⻓任务阻塞主线程⽽导致的延迟。

# 获取 TBT 时间

获取 TBT 的标准⽅法是使⽤浏览器的 Performance API 来监控⻓任务。⼀个⻓任务是指超过 50 毫秒的任务,它们可能阻塞主线程,导致⽤户体验不佳。以下是⼀个示例代码:

let totalBlockingTime = 0;

new PerformanceObserver((list) => {
  list.getEntries().forEach((entry) => {
    if (entry.entryType !== "longtask") return;
    const blockingTime = entry.duration - 50; // 只计算超过 50 毫秒的部;
    totalBlockingTime += blockingTime > 0 ? blockingTime : 0;
  });
}).observe({ type: "longtask", buffered: true });

window.addEventListener("load", () => {
  console.log("Total Blocking Time:", totalBlockingTime);
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这段代码将计算⻚⾯加载期间所有⻓任务的总阻塞时间。

# TBT 的指标分数段

TBT 的分数段通常如下:

  • 好:⼩于 300 毫秒。

  • 需要改进:300 毫秒到 600 毫秒之间。

  • 差:超过 600 毫秒。

# 如何提升 TBT

提升 TBT 的关键在于减少主线程上的⻓任务,确保在⻚⾯加载期间⽤户能够尽快与⻚⾯交互。这通常涉及到 JavaScript、CSS 和 DOM 的优化,以及加载策略的调整。

1. 优化 JavaScript 执⾏

  • 减少和拆分⻓任务。

  • 使⽤代码分割和延迟加载⾮关键 JavaScript。

  • 优化代码执⾏效率。

2. 减少主线程⼯作量

  • 优化 CSS 计算。

  • 减少 DOM 操作。

  • 避免不必要的重绘和重排。

3. 使⽤ Web Workers

将复杂计算移⾄ Web Workers,以减轻主线程负担。

4. 优化第三⽅脚本加载

  • 减少不必要的第三⽅脚本。

  • 延迟加载第三⽅资源。

5. 服务器端渲染(SSR)

使⽤服务器端渲染⽣成静态内容,减少客户端渲染时间。

6. 优化资源加载

  • 预加载关键资源。

  • 使⽤ CDN 加速内容分发。

# 为什么会白屏

无非就是两个方面的原因,网络层面和渲染层面。网络层面可能是获取 CSS 和 JS 资源的时间太长,渲染层面就是 CSS 和 JS 的摆放位置会对页面渲染有影响,JS 放在顶部会对 DOM 解析和渲染都有影响,放在底部的话虽然不影响 DOM 解析,但是也会影响 DOM 渲染。CSS 也会影响 DOM 渲染。

# 浏览器中的 API

W3C Navigation Timing (opens new window)

Navigation Timing API (opens new window)

performance

上面这张图展示的是从用户访问一个网页(敲回车或者点击)到整个网页展示出来的整个过程,这个过程中有各种浏览器的 API 可供我们使用。通过这张图并配合 Performance.timing (opens new window) 我们还可以拿到其他的一些指标。比如:

let t = performance.timing;
console.log(
  "DNS 查询耗时 :" + (t.domainLookupEnd - t.domainLookupStart).toFixed(0)
);
console.log("TCP 连接耗时 :" + (t.connectEnd - t.connectStart).toFixed(0));
console.log(
  "request 请求耗时 :" + (t.responseEnd - t.responseStart).toFixed(0)
);
console.log(
  "解析 dom 树耗时 :" + (t.domComplete - t.domInteractive).toFixed(0)
);
console.log("白屏时间 :" + (t.responseStart - t.navigationStart).toFixed(0));
console.log(
  "domready 时间 :" +
    (t.domContentLoadedEventEnd - t.navigationStart).toFixed(0)
);
console.log("onload 时间 :" + (t.loadEventEnd - t.navigationStart).toFixed(0));
if ((t = performance.memory)) {
  console.log(
    "js 内存使用占比 :" +
      ((t.usedJSHeapSize / t.totalJSHeapSize) * 100).toFixed(2) +
      "%"
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

Performance API (opens new window) 提供了获取性能信息的各项 API。比如,可以用 PerformanceObserver (opens new window) 来监测性能事件。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      body {
        background: gray;
      }
    </style>
  </head>
  <body>
    <div id="app">
      123
      <h1>深圳</h1>
      <script>
        // FMP 没有比较好的库可以获取,因此可以自己用 mark 去定义获取
        performance.mark("shenzhen");
      </script>
    </div>
    <script>
      // for(){
      // }
      const observer = new PerformanceObserver((list) => {
        for (const entry of list.getEntries()) {
          console.log(entry.name);
          console.log(entry.startTime);
          console.log(entry.duration);
        }
      });
      observer.observe({ entryTypes: ["paint", "mark", "longtask"] });
    </script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# SPA 和 MPA

# SPA

  • 单页应用又称 SPA(Single Page Application)指的是使用单个 html 完成多个页面切换和功能的应用。这些应用只有一个 html 文件作为入口,一开始只需加载一次 js、css 等相关资源,然后使用 js 完成页面的布局和渲染,页面展示和功能是根据路由完成的。

  • 单页应用跳转,就是切换相关组件,仅刷新局部资源。

# MPA

  • 多页应用又称 MPA(Multi Page Application)指有多个独立的页面的应用,每个页面必须重复加载 js、css 等相关资源。

  • 多页应用跳转,需要整页资源刷新。

# 各种渲染方式优缺点

# CSR

客户端渲染就是指将页面数据和页面模板组装成 html 的过程在客户端进行,服务器直接转发静态 html 资源即可。

打包的时候生成只有 css、js 等外链标签的空白 html 页面,客户端在请求时,服务端不做任何处理,直接以原文件的形式返回给客户端,客户端获取到页面后,等加载完 js 后才通过 js 来渲染页面内容。

# 预渲染

构建阶段生成匹配预渲染路径的 html 文件,每个需要预渲染的路由都有一个对应的 html,构建出来的 html 文件已有部分内容。

如果项目中有使用 webpack,可以使用 prerender-spa-plugin (opens new window) 轻松添加预渲染。

# SSR

服务端渲染就是指将页面数据和页面模板组装成 html 的过程在服务端进行,客户端不需要渲染页面。

服务端渲染模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 html 字符串,然后把它返回给客户端。客户端拿到手的,是可以直接渲染然后呈现给用户的 html 内容,不需要为了生成 dom 内容自己再去跑一遍 js 代码。使用服务端渲染的网站,可以说是 “所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到。

# 同构渲染

同构是指写一份代码但可同时在浏览器和服务器中运行的应用。

为了同时拥有 ssr 和 csr 的特点,当前流行的方案就是 ssr + csr 同构,比如现在比较流行的的 Next.js (opens new window)

同构渲染就是指在服务端先进行渲染一次(ssr,组装页面 html 内容),客户端拿到代码后,再进行渲染一次(csh(client-side hydration),也就是 hydrate,主要对 html 进行事件绑定和内容校验,如果 hydrate 发现内容不一致的话,会在开发环境提示警告),后续页面的所有操作和渲染行为都和 csr 一致(DidMount 后的更新页面内容都属于正常的 csr 了)。

performance

上次更新时间: 2024年01月06日 16:01:11