以用户视觉,开启性能优化盛宴...

390 阅读5分钟

前言

网站的性能跟用户的体验息息相关,用户体验差,客流量就会降低,所以在面试中也经常会被问:“你做过那些性能优化....”,诸如此类的问题,下面小编以自己的项目为例,从时间和空间维度介绍如何从用户视觉开启性能优化的视觉盛宴...

性能指标

常见的性能指标有以下几个:

  • FP(first-paint) ---> 页面首次绘制元素时间

  • FCP(first-contentful-paint) ---> 页面内容首次加载时间

  • LCP(largest-contentful-paint) ---> 最大内容加载时间

  • FID(first-input-delay) ---> 用户首次交互到浏览器响应时间

  • CLS ---> 累计位移偏移,页面上非预期位移波动

构建

在使用 build 命令打包项目之后,可以看到打包完的 js 文件夹只有一个 .js 文件,里面包含业务代码、第三方依赖,导致该文件体积臃肿

image.png

如图,在项目打包完之后的 js 体积接近 2.4M,可以想象一个请求传输的资源体积这么大,耗时可谓相当可怕

屏幕截图 2025-05-24 141649.png

可以看到一个 2.5M 的包在上线后的加载速度高达 3秒半。

问题来了:如何减小包体积加快请求响应时间呢?

聪明的小伙伴应该想到可以通过 gzip 压缩包体积,该项目用的是 webpack ,那小编就使用compression-webpack-plugin 实施

// 通过 npm 下载包后,添加该配置
const CompressionWebpackPlugin = require('compression-webpack-plugin');

(config) => {
    if (process.env.NODE_ENV === 'production') {
      config.plugins.push(
        new CompressionWebpackPlugin({
          test: /\.js$|\.css$/, // 压缩 js 和 css
          filename: '[path][base].gz', // 输出 .gz 文件
          algorithm: 'gzip', 
          threshold: 10240, // 文件大于10KB压缩
          minRatio: 0.8, // 当压缩后的文件大小小于原始文件大小的 80% 时,才会保留该压缩文件。
          deleteOriginalAssets: false, // 删除原文件
        })
      );
    }
    return config;
  },

有些浏览器默认支持gzip压缩,可以通过网络条件配置,如图:

屏幕截图 2025-05-24 142805.png

浏览器支持gzip压缩后,在响应头会多出 content-encoding 字段,如图:

屏幕截图 2025-05-24 142758.png

我们再回来看压缩后的体积以及响应速度

屏幕截图 2025-05-24 142749.png

成功了,可以看到响应速度由原来的3秒半缩短到1秒,但是响应时间还是很久。

那除了压缩还有什么方法?

小编想到 http 可以支持并发请求,在打包的时候如果按照不同功能分成不同的包如:核心库(react、react-dom、vue、vue-router...),体积大的包如(echarts、lodash...),业务包...等等,在请求的时候分别请求不同的包是不是就大大加快了响应时间。

// 拆分 chunks
function splitChunks(config) {
  config.optimization = {
    ...config.optimization,
    splitChunks: {
      cacheGroups: {
        // React 相关库单独打包
        react: {
          test: /[\\/]node_modules[\\/](react|react-dom|react-router|react-router-dom)[\\/]/,
          name: 'react',
          chunks: 'all',
          priority: 30,
          enforce: true,
        },
        // lodash 单独打包
        lodash: {
          test: /[\\/]node_modules[\\/]lodash[\\/]/,
          name: 'lodash',
          chunks: 'all',
          priority: 25,
          enforce: true,
        },
        // echarts 及其依赖单独打包
        echarts: {
          test: /[\\/]node_modules[\\/](echarts|zrender)[\\/]/,
          name: 'echarts',
          chunks: 'all',
          priority: 20,
          enforce: true,
        },
        // 业务代码公共部分打包进 common.js
        common: {
          name: 'common',
          minChunks: 2,
          chunks: 'all',
          reuseExistingChunk: true,
          priority: 10,
        },
        // 其它第三方依赖打包进 vendors.js
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendors',
          chunks: 'all',
          priority: 0,
        },
      },
    },
  };
  return config;
}

分包主要做了什么优化:

  1. 加快构建速度,每一个包的构建相互独立并发构建,充分利用了多核 CPU
  2. 当依赖或业务代码发生改变,无需重新构建所有依赖,只需改变对应的包
  3. 利用构建工具的缓存功能,没有改变的依赖在下一次构建时直接读取缓存
  4. 减小包体积,并利用请求并发,在线上可以并发请求各个包,加快加载速度

结合分包及 gzip 压缩后效果如图:

image.png 可以看到请求是并发进行的互不干扰,减少了白屏时间,提高用户体验。

这时候可能会有人想到,对每个页面的代码及依赖进行分包,并对每个网页都进行组件懒加载,这样在需要某个部分的代码时加载对应的文件,岂不美哉?

小编很久之前就做过这样的事,结果网络请求太多,网络负荷过高,得不偿失。

适合组件懒加载的场景(常考题):

  1. 某个页面的 JS 文件体积大,导致页面白屏时间长,通过组件懒加载进行资源拆分,利用浏览器并行下载资源,提升下载速度(比如首页)
  2. 该组件不是一进入页面就展示,需要一定条件下才触发(弹框组件)
  3. 该组件复用性高,很多页面都有引入,利用组件懒加载抽离出该组件,一方面可以很好利用缓存,同时也可以减少页面的 JS 文件大小(比如表格组件、图形组件等)

尽管进行压缩以及分包,但是效果似乎并没有那么出色,那还有什么方法呢?

我们接着观察打包后的产物,可以发现单独一个 echarts 的包就高于1M,那是不是可以在 echarts 上面做文章

屏幕截图 2025-05-26 134130.png

如何减少echarts包的体积呢?应该很多小伙伴已经想到了吧,没错!就是按需引入我们需要的部分。因为该项目只用了饼图,所以就只引入饼图相关内容即可:

// echarts.js
import * as echarts from 'echarts/core'; // 引入 ECharts 的核心模块,包含基础功能和框架。
import { PieChart  } from 'echarts/charts'; // 引入饼图组件
import { TitleComponent, TooltipComponent, GridComponent, DatasetComponent, TransformComponent } from 'echarts/components'; // 引入常用的 ECharts 组件
import { LabelLayout, UniversalTransition } from 'echarts/features'; // 引入可选功能
import { CanvasRenderer } from 'echarts/renderers'; // 引入 Canvas 渲染器

echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
PieChart,
LabelLayout,
UniversalTransition,
CanvasRenderer
]);

export default echarts;

接着只需在组件部分引入该 echarts.js 就能够正常使用了,实现后包体积减小了一半,效果如图:

屏幕截图 2025-05-26 134726.png

我们再利用 lightHouse 插件对网页进行分析,如图:

屏幕截图 2025-05-24 164809.png

可以看到是由媒体资源引起的 LCP(最大内容渲染时间)过久

image.png

接下来对该图片资源进行分析:

  • 内容大小远高于呈现大小(即分辨率)
  • 分辨率过高导致图片体积大

解决方案:图片格式转换 png -> webp,图片预加载

屏幕截图 2025-05-24 172916.png

转换之后效果如图,对于网页展示效果并不影响,大大减小了图片体积

image.png

在入口文件index.html中通过link对图片进行预加载 <link rel="preload" as="image" href="..." />优化后效果如图:

屏幕截图 2025-05-24 191814.png