从Lighthouse开始聊聊前端性能指标

382 阅读10分钟

经常做性能优化和性能监控的朋友们应该知道,用户对于软件和网页的使用体验非常重要,常常会决定用户对软件的选择,那么怎么样量化用户的体验呢?性能指标是我们常聊的话题,那么今天哈士奇想从Lighthouse开始和大家聊聊性能指标。

Lighthouse 简介

Lighthouse 是一个由 Google 开发的开源自动化工具,用于评估网页的性能、可访问性、最佳实践和 SEO 等方面。它可以在 Chrome 浏览器的开发者工具中运行,也可以作为命令行工具或通过 Node.js 模块使用。Lighthouse 会对网页进行一系列的测试,并生成一份详细的报告,其中包括各个性能指标的得分、问题列表和优化建议。

image.png

Lighthouse 的主要性能指标

  1. 性能(Performance) :衡量网页的加载速度和响应时间。
  2. 可访问性(Accessibility) :评估网页对于残障用户的可访问性。
  3. 最佳实践(Best Practices) :检查网页是否遵循了前端开发的最佳实践。
  4. SEO(Search Engine Optimization) :分析网页的搜索引擎优化情况。 image.png

Lighthouse 的工作原理

Lighthouse 会模拟真实用户的行为,对网页进行加载和交互操作。它会收集各种性能数据,如加载时间、资源大小、请求数量等,并根据预设的规则和算法对这些数据进行分析和评估。最后,它会生成一份详细的报告,其中包括各个性能指标的得分、问题列表和优化建议。

性能指标详解

首次内容绘制(First Contentful Paint,FCP)

定义:首次内容绘制是指浏览器从开始加载网页到首次在屏幕上绘制任何内容的时间。这个时间通常包括 HTML 文档的下载、解析和渲染,以及 CSS 和 JavaScript 文件的下载和执行。

image.png

影响因素

  • 网络延迟:网络延迟越高,网页的加载速度就越慢,FCP 时间也就越长。
  • 资源大小:网页中的资源(如图片、脚本、样式表等)越大,下载时间就越长,FCP 时间也就越长。
  • 脚本执行时间:如果网页中的 JavaScript 脚本执行时间过长,会阻塞浏览器的渲染进程,导致 FCP 时间延长。

提升方法

  • 优化网络请求:减少不必要的网络请求,压缩资源大小,使用 CDN 加速等。
  • 延迟加载非关键资源:对于一些非关键的资源(如图片、视频等),可以延迟加载,等到页面主要内容加载完成后再进行加载。
  • 优化脚本执行:减少不必要的脚本执行,使用异步加载和 defer 属性等。

示例代码

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Example</title>
  <link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'">
</head>

<body>
  <h1>Hello World!</h1>
  <img src="image.jpg" alt="Image" loading="lazy">
  <script src="script.js" defer></script>
</body>

</html>

在上面的代码中,我们使用了media属性和onload事件来延迟加载样式表,直到页面打印时才加载。同时,我们使用了loading="lazy"属性来延迟加载图片,直到图片进入视口时才加载。最后,我们使用了defer属性来延迟加载脚本,直到页面加载完成后再执行。

最大内容绘制(Largest Contentful Paint,LCP)

  1. 定义:最大内容绘制是指浏览器从开始加载网页到绘制最大内容元素的时间。这个时间通常包括 HTML 文档的下载、解析和渲染,以及 CSS 和 JavaScript 文件的下载和执行。最大内容元素通常是指页面中最大的图片、视频、文本块等。

image.png

影响因素

  • 资源大小:网页中的资源(如图片、脚本、样式表等)越大,下载时间就越长,LCP 时间也就越长。
  • 脚本执行时间:如果网页中的 JavaScript 脚本执行时间过长,会阻塞浏览器的渲染进程,导致 LCP 时间延长。
  • 网络延迟:网络延迟越高,网页的加载速度就越慢,LCP 时间也就越长。

提升方法

  • 优化资源大小:压缩图片、脚本、样式表等资源的大小,减少下载时间。
  • 延迟加载非关键资源:对于一些非关键的资源(如图片、视频等),可以延迟加载,等到页面主要内容加载完成后再进行加载。
  • 优化脚本执行:减少不必要的脚本执行,使用异步加载和 defer 属性等。

示例代码

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Example</title>
  <link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'">
</head>

<body>
  <h1>Hello World!</h1>
  <img src="image.jpg" alt="Image" loading="lazy">
  <script src="script.js" defer></script>  //使用defer延迟加载
</body>

</html>

在上面的代码中,我们使用了media属性和onload事件来延迟加载样式表,直到页面打印时才加载。同时,我们使用了loading="lazy"属性来延迟加载图片,直到图片进入视口时才加载。最后,我们使用了defer属性来延迟加载脚本,直到页面加载完成后再执行。

累积布局偏移(Cumulative Layout Shift,CLS)

定义:累积布局偏移是指在网页加载过程中,由于元素的位置发生变化而导致的布局偏移的总和。这个指标衡量了网页的视觉稳定性,CLS 值越低,网页的视觉稳定性就越好。

image.png 影响因素

  • 动态加载资源:如果网页在加载过程中动态加载了资源(如图片、脚本、样式表等),可能会导致元素的位置发生变化,从而产生布局偏移。
  • 脚本执行时间:如果网页中的 JavaScript 脚本执行时间过长,可能会导致元素的位置发生变化,从而产生布局偏移。
  • 网络延迟:网络延迟越高,网页的加载速度就越慢,可能会导致元素的位置发生变化,从而产生布局偏移。

提升方法

  • 固定元素位置:对于一些关键的元素,可以使用position: fixedposition: sticky等属性来固定它们的位置,避免在加载过程中发生位置变化。
  • 延迟加载资源:对于一些非关键的资源,可以延迟加载,等到页面主要内容加载完成后再进行加载,避免在加载过程中影响布局。
  • 优化脚本执行:减少不必要的脚本执行,避免在加载过程中影响布局。

示例代码

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Example</title>
  <style>
   .fixed-element {
      position: fixed;
      top: 0;
      left: 0;
      width: 100px;
      height: 100px;
      background-color: blue;
    }
  </style>
</head>

<body>
  <h1>Hello World!</h1>
  <div class="fixed-element"></div>
  <img src="image.jpg" alt="Image" loading="lazy">
  <script src="script.js" defer></script>
</body>

</html>

在上面的代码中,我们使用了position: fixed属性来固定一个蓝色的元素的位置,避免在加载过程中发生位置变化。同时,我们使用了loading="lazy"属性来延迟加载图片,直到图片进入视口时才加载。最后,我们使用了defer属性来延迟加载脚本,直到页面加载完成后再执行。

首次输入延迟(First Input Delay,FID)

定义:首次输入延迟是指从用户首次与网页交互(如点击链接、输入文本等)到浏览器实际响应这个交互的时间。这个指标衡量了网页的交互响应速度,FID 值越低,网页的交互响应速度就越快。

影响因素

  • 脚本执行时间:如果网页中的 JavaScript 脚本执行时间过长,会阻塞浏览器的主线程,导致用户交互无法及时响应,从而产生较高的 FID 值。
  • 资源加载时间:如果网页中的资源(如图片、脚本、样式表等)加载时间过长,会导致浏览器在处理用户交互时出现延迟,从而产生较高的 FID 值。
  • 网络延迟:网络延迟越高,网页的加载速度就越慢,可能会导致用户交互无法及时响应,从而产生较高的 FID 值。

提升方法

  • 优化脚本执行:减少不必要的脚本执行,使用异步加载和 defer 属性等,避免阻塞浏览器的主线程。
  • 优化资源加载:压缩资源大小,使用 CDN 加速等,减少资源加载时间。
  • 减少网络延迟:使用更快的网络连接,优化服务器响应时间等,减少网络延迟。

示例代码

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Example</title>
  <link rel="stylesheet" href="styles.css" media="print" onload="this.media='all'">
</head>

<body>
  <h1>Hello World!</h1>
  <button onclick="handleClick()">Click me</button>
  <img src="image.jpg" alt="Image" loading="lazy">
  <script src="script.js" defer></script>
  <script>
    function handleClick() {
      console.log('Button clicked!');
    }
  </script>
</body>

</html>

在上面的代码中,我们使用了onclick事件来处理按钮的点击事件。同时,我们使用了loading="lazy"属性来延迟加载图片,直到图片进入视口时才加载。最后,我们使用了defer属性来延迟加载脚本,直到页面加载完成后再执行。

总阻塞时间(Total Blocking Time,TBT)

定义: Total Blocking Time 指的是在主线程被阻塞的总时间。在网页加载和交互过程中,浏览器的主线程负责处理各种任务,如解析 HTML、执行 JavaScript、渲染页面等。当主线程被阻塞时,用户的交互和页面的更新都会受到影响,导致页面响应变慢,用户体验下降。 image.png

影响因素:

  • 长任务执行:如果网页中有长时间运行的任务,如复杂的 JavaScript 计算、大量的 DOM 操作等,这些任务会占用主线程,导致主线程被阻塞。(例如一次画十万个圆)
  • 同步加载脚本:当网页加载同步脚本时,浏览器会暂停其他任务,直到脚本下载并执行完成。这会导致主线程被阻塞,增加 TBT。
  • 资源加载顺序不当:如果关键资源(如 CSS 和 JavaScript 文件)的加载顺序不合理,可能会导致主线程在等待资源加载时被阻塞。

提升方法:

  • 任务拆分:将长任务拆分成多个小任务,利用浏览器的空闲时间逐步执行。
  1. 例如,可以使用 requestIdleCallback 函数在浏览器空闲时执行任务。
function longTask() {
  let total = 0;
  for (let i = 0; i < 1000000; i++) {
    total += Math.pow(i, 2);
    if (i % 1000 === 0 && window.requestIdleCallback) {
      window.requestIdleCallback(() => {
        // 可以在这里继续执行下一部分任务
      });
    }
  }
  return total;
}
  1. 使用 Web Workers:将一些耗时的计算任务转移到 Web Workers 中执行,这样不会阻塞主线程。
// 创建一个 Web Worker
const worker = new Worker('worker.js');

// 在主线程中向 Web Worker 发送消息
worker.postMessage({ data: [1, 2, 3, 4, 5] });

// 在 Web Worker 中接收消息并进行处理
self.onmessage = function(event) {
  const data = event.data;
  const result = data.map(item => item * 2);
  // 将处理结果发送回主线程
  self.postMessage(result);
};
  • 异步加载脚本
  1. 使用 async 和 defer 属性:对于非关键的脚本,可以使用 async 属性使其异步加载,不阻塞页面的渲染。对于需要在页面加载完成后执行的脚本,可以使用 defer 属性。
<script src="script.js" async></script>
<script src="anotherScript.js" defer></script>
  1. 动态加载脚本:在需要的时候动态加载脚本,而不是在页面加载时一次性加载所有脚本。
function loadScript(url) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.src = url;
    script.onload = resolve;
    script.onerror = reject;
    document.head.appendChild(script);
  });
}

// 在需要的时候加载脚本
loadScript('script.js').then(() => {
  console.log('Script loaded successfully.');
});
  • 优化资源加载顺序
  1. 合理安排 CSS 和 JavaScript 文件的加载顺序:确保关键的 CSS 文件在页面渲染之前加载完成,而 JavaScript 文件在页面加载完成后或者在需要的时候加载。
  2. 使用预加载:对于关键资源,可以使用 <link rel="preload"> 标签进行预加载,这样可以在浏览器空闲时提前下载资源,减少等待时间。
<head>
  <link rel="preload" href="styles.css" as="style">
  <link rel="preload" href="script.js" as="script">
</head>
<body>
  <!-- 页面内容 -->
</body>

总结

总的来说,在我们写前端页面的过程中,除了复杂的页面以外,也需要考虑到整体的用户体验。这些指标可以帮助我们很好把控到影响用户体验的直观感受,后面有机会的话,哈士奇会针对这些标准再深入写一些内容和大家聊聊