【性能优化篇】-根据重要指标性能优化

270 阅读9分钟

测量工具

  • Chrome 浏览器:  lighthouse, performance
  • PerformanceObserver API

重要指标

LCP

  • 指视图中最大图像和文本块呈现的时间。

最大内容绘制,LCP(Largest Contentful Paint),用于记录视窗内最大的元素绘制的时间,该时间会随着页面渲染变化而变化,因为页面中的最大元素在渲染过程中可能会发生改变,另外该指标会在用户第一次交互后停止记录。

LCP时间颜色分数评级
0-2.5秒绿色75~100分良好
2.6-4.0秒橙色50~74分需要改进
4.1秒以上红色0~49分较差

LCP 分析会考虑到其找到的所有内容,甚至包括已从 DOM 中删除的内容。每当发现新的最大内容时,它都会创建一个新条目,因此可能会存在多个对象。然而,当发生滚动或输入事件时,LCP 分析会停止搜索更大的内容。因此,一般来说,LCP 数据会取最后一个找到的内容作为结果。

const observer = new PerformanceObserver((list) => {
    const entries = list.getEntries();
    const lastEntry = entries[entries.length - 1];
    console.log(lastEntry);
});

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

以下是对于给定指标的解释和展示对象的描述:

  • element : 当前最大的内容绘制元素
  • loadingTime 即LCP: 加载时间
  • renderTime: 渲染时间。如果是跨域请求,则为 0。
  • size: 元素本身的面积
  • startTime: 如果 renderTime 不为 0,则返回 renderTime;否则返回 loadingTime

FCP

  • 指的是任何内容的一部分首次在屏幕上呈现的时间。您可以通过查看截图来了解其标准。与之类似的还有一个指标是 FP(First Paint) ,表示第一个像素绘制到屏幕上的时间。
FCP时间颜色分数评级
0-1.8秒绿色75~100分良好
1.9-3.0秒橙色50~74分需要改进
3.1秒以上红色0~49分较差
const observer = new PerformanceObserver((list) => {
    list.getEntries().forEach((entry) => {
        console.log(entry);
    });
});

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

在给定的指标中:

  • duration: 持续时间,这里是 0。
  • startTime 即FCP: 返回绘制发生时的时间戳。

FID

  • 指的是用户首次与页面交互到浏览器实际能够开始处理程序以响应该交互的时间。FID 测量接收到输入事件与主线程下次空闲之间的增量。即使在未注册事件侦听器的情况下,也会测量 FID。此外,FID 仅关注离散事件操作,例如点击、触摸和按键等。与之不同的是,缩放和滚动以及持续性事件(如 mousemove、pointermove、touchmove、wheel 和 drag)不包括在这个指标中。
FID时间颜色分数评级
0-100毫秒绿色75~100分良好
101-300毫秒橙色50~74分需要改进
300毫秒以上红色0~49分较差
const observer = new PerformanceObserver((list) => {
    list.getEntries().forEach((entry) => {
        const FID = entry.processingStart - entry.startTime;
        console.log(entry);
    });
});

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

在给定的指标中:

  • duration: 表示从 startTime 到下一个渲染绘制的时间。
  • processingStart : 测量用户操作与事件处理程序开始运行之间的时间。
  • processingEnd : 测量事件处理程序运行所花费的时间。
  • color: 返回关联事件的 DOM。

FID 等于 processingEnd - processingStart

CLS

  • 衡量页面在其整个生命周期内发生的最大意外布局偏移分数。在此评估中,仅考虑元素改变其初始位置的情况,对于增加新元素到 DOM 或元素的宽度、高度改变等情况不予以计算。
CLS颜色分数评级
0-0.1绿色75~100分良好
0.1-0.25橙色50~74分需要改进
> 0.25红色0~49分较差
const observer = new PerformanceObserver((list) => {
    list.getEntries().forEach((entry) => {
        console.log(entry);
    });
});

observer.observe({ type: 'layout-shift', buffered: true });

有如下几个指标:

  • color: 返回偏移分数,计算方式为:layout shift score = impact fraction * distance fraction。
  • hadRecentInput: 如果 lastInputTime 过去小于 500 毫秒,则返回 true。
  • lastInputTime: 返回最近排除的用户输入的时间,如果没有则返回 0。仅考虑那些用户未期望的偏移,例如在响应用户互动时发生的离散事件,如点击链接、点击按钮或请求 API 时显示加载等。这些情况被视为合理的偏移。

CLS 为 value

Long Task

  • 阻塞主线程超过 50 毫秒的长任务,可能导致多种不良影响,包括响应事件的延迟和动画卡顿。当主线程被长任务占用时,浏览器无法及时响应用户输入和处理其他事件,从而影响了用户体验。

主要原因可能是:

  • 长时间运行的事件处理程序(Long-running event handlers)
  • 昂贵的回流(reflow)和其他重新渲染操作,例如 DOM 操作、动画等
  • 超过 50 毫秒的长时间循环
const observer = new PerformanceObserver((list) => {
    list.getEntries().forEach((entry) => {
        console.log(entry);
    });
});

observer.observe({ type: 'longtask', buffered: true });

其如下几个指标:

  • duration:表示任务的持续时间,即从开始到结束所经过的时间。
  • TaskAttributionTiming:是与长任务(Long Task)相关的对象,用于追踪和归因长任务的执行。这个对象可能包含关于长任务的详细信息,例如任务的来源、触发事件等。通过这个对象,开发人员可以更好地了解长任务的上下文和原因,从而进行性能优化和调试。

因为长任务(Long Task)对用户体验有显著影响,所以即使它不是 Web Vitals 的一部分,也将其单独列出来。

优化措施

代码分割和懒加载

// React
const DownloadFile = lazy(() => import('./page/OperateFile/OperateFile'));  
const TimeSelect = lazy(() => import('./page/TimeSelect/TimeSelect'));  
  
export const router: Router[] = [  
    {  
        path: '/',  
        element: <App />,  
        name: 'Home',  
    },  
    {  
        path: '/download-file',  
        element: <DownloadFile />,  
        name: 'Download File',  
    },  
    {  
        path: '/time-select',  
        element: <TimeSelect />,  
        name: 'Time Select',  
    },  
];

Img

Attributes

  1. loading 属性告知浏览器如何加载图片。
    • eager:立即加载图片,无论其是否可见。
    • lazy:延迟加载图片,直到图片出现在视口,可节省带宽。建议同时为图片添加宽高属性。
  2. fetchpriority 属性可指定图片加载的优先级

通过根据图片的业务价值使用这些属性,可以优化 Web Core Vitals 指标,提升整体性能。此外,提前加载关键图片资源也可使用link** 标签。

<link rel="preload" fetchpriority="high" as="image" href="image.webp" type="image/webp">
  1. size
-   图像不应提供大于用户屏幕上呈现的版本。
-   使用响应式图像,指定多个图像版本,浏览器会选择使用最佳版本。

<img src="flower-large.jpg" srcset="example-small.jpg 480w, example-large.jpg 1080w" sizes="50vw">

480w 是指告知浏览器在不需要下载图片的情况下,就知道宽度是 480px;sizes 指定图片预期显示大小 可以使用 svg,可以无限缩放

  1. width 和 height:
-   应该同时指定适当的`width`和`height` 属性,以确保浏览器在布局中分配正确的空间。这有助于避免布局偏移,提高 Cumulative Layout Shift(CLS)的用户体验。
-   如果无法确定具体的宽度和高度,可以考虑设置宽高比例,以提供一种解决方案

img {
  aspect-ratio: 16 / 9;
  width100%;
  object-fit: cover;
}
```
  1. Decoding 该属性提供了对浏览器的提示,指示它应该如何解码图像。更具体地说,它指定是等待图像解码完成后再呈现其他内容更新,还是允许在解码过程中同时呈现其他内容。
    -   {% mark sync color:green %}:同步解码图像,以便与其他内容一同呈现。
    -   {% mark async color:green %}:异步解码图像,并允许在其完成之前呈现其他内容。
    -   {% mark auto color:green %}:对解码模式没有偏好;浏览器决定对用户最有利的方式。这是默认值,但不同的浏览器有不同的默认值:

    Chromium 默认为 sync,Firefox 默认为 async,Safari 默认为 sync。`decoding` 属性的效果可能仅在非常大、高分辨率的图像上才会显著,因为这些图像的解码时间较长。

Video

Preload

属性是为了向浏览器提供有关作者认为在视频播放之前加载哪些内容会导致最佳用户体验的提示。它可以具有以下值:

  • none: 表示视频不应预加载。
  • metadata: 表示仅获取视频元数据(例如长度)。
  • auto: 表示整个视频文件可以下载,即使用户预计不会使用它。
  • 空字符串: auto 值的同义词。

每个浏览器的默认值不同。规范建议将其设置为 metadata。具体来说比如想要推迟视频的加载,可以写成这样:

<video controls preload="none" poster="placeholder.jpg">
  <source src="video.mp4" type="video/mp4">
	<p>
    Your browser doesn't support HTML video. Here is a
    <a href="myVideo.mp4" download="myVideo.mp4">link to the video</a> instead.
  </p>
</video>

Http 缓存

利用强缓存和协商缓存

CDN

通过降低往返时间(RTT),以及采用 HTTP/2 或 HTTP/3、缓存和压缩等优化策略,CDN 能够更快地提供内容,改善用户的访问体验。

最小化代码

JavaScript

使用 Terser 工具来实现,主要包括移除未使用的代码(Tree Shaking)、缩短变量名以及删除空格、Uglifiers 等操作,以降低代码体积、减少下载时间。

Style

通常会利用mini-css-extract-plugin 插件进行优化。该插件能够独立地从每个包含 CSS 的 JavaScript 文件中提取出一个单独的 CSS 文件,实现样式的独立加载。更进一步,该插件支持按需加载和 Source Map,为样式管理提供了更加灵活和高效的方式。

Image source

使用 WebP 格式代替 JPEG 和 PNG 可以显著减少文件大小,通常能够实现 25%-35% 的减少。同时,使用内容交付网络(CDN)对于图片加载的优化效果显著,通常能够实现 40%-80%的图片大小节省。

避免长任务,代码优化

Long Task 指的是执行时间超过 50 毫秒的任务,可以通过以下方式在 Main Thread 上释放:

  • Web Workers 是在后台运行的独立线程,拥有自己的堆栈、堆内存和消息队列。与主线程进行通信只能通过postMessage** 方法发送消息,而无法直接操作 DOM。因此,Web Workers 极为适合执行那些不需要与 DOM 直接交互的任务。例如,对大规模数据进行排序、搜索等操作可以放在 Web Worker 中执行,从而避免了这些计算密集型任务对主线程的阻塞,确保主线程保持响应性。

  • 为了确保长时间运行的任务不会阻塞主线程,我们可以采用将这些长任务拆分成小的、异步执行的子任务的策略。

  • 对于 UI 改动,推荐使用requestAnimationFramerequestAnimationFrame可以确保回调在浏览器准备好进行下一次重绘时执行,使得动画效果更加流畅。