在实践后发现每一个维度,每一种优化方案下都有非常多的技术细节,如果要全面极致的做好优化,不仅是从全局到细节、从技术储备的广度到深度的考验,很多的优化手段或者方向,如果没有实践可能完全会是盲区。狭义的讲优化无非是用更少的时间做更多的事,从各种角度减少不必要的资源(网络、渲染、I/O 等)开销。更宽泛的讲,我认为其实上到页面合理直接的交互设计,操作逻辑的去繁从简也可以包括在优化之中,这么说好像其实是整个系统工程的整体优化,最终体现到网页的加载速度上。
性能优化指标
FCP
First Contentful Paint 标记了绘制出首个文本或首张图片的时间
FCP 衡量的是用户转到您的网页后,浏览器渲染第一段 DOM 内容所用的时间。 您网页上的图片、非白色 <canvas>
元素及 SVG 都被视为 DOM 内容;iframe 中的任何内容均不会包含在其中
FCP 优化方案
移除阻塞渲染的资源
视为阻塞渲染的资源
<script>
- 在文档的
<head>
中 - 没有
defer
属性 - 没有
async
属性
<link rel="stylesheet">
- 没有
disabled
属性 - 没有
media
属性 - 有
media="all"
属性
缩减 CSS 大小
移除未使用的 CSS
移除未使用的 JavaScript
预先连接到所需的源
缩短服务器响应时间 (TTFB)
避免多次网页重定向
预加载密钥请求
避免网络负载庞大
采用高效的缓存政策提供静态资源
避免 DOM 规模过大
最大限度地缩短关键请求深度
确保文本在网页字体加载期间保持可见状态
尽量减少请求数量,减少传输大小
计算 FCP
使用 JavaScript 获取 FCP 时间
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntriesByName("first-contentful-paint")) {
console.log("FCP candidate:", entry.startTime, entry);
}
}).observe({ type: "paint", buffered: true });
API 与指标计算方式之间的差异
- API 会为后台标签页加载的网页分配
first-contentful-paint
,计算 FCP 时应该忽略 - 从 往返缓存 中恢复网页时,API 不会报告
first-contentful-paint
条目,计算 FCP 时应该要考虑到这种访问情况 - API 可能不会报告跨源 iframe 的绘制时间
- API 从导航启动时开始测量 FCP,但对于预渲染的网页,应通过 activationStart 测量 FCP
实际使用时还是用现成的包检查计算 FCP 的差异吧~
import { onFCP } from "web-vitals";
// Measure and log FCP as soon as it's available.
onFCP(console.log);
优化方案细节
资源压缩-[源码]
gzip 压缩
vite 开启 gzip 压缩
vite-plugin-compression
gzip 压缩
@rollup/plugin-terser
Terser 优化
webpack 等打包工具等大同小异,忽略
nginx 配置 gzip_static
-
nginx 动态压缩 (对请求先压缩再输出)
-
nginx 静态压缩 (直接输出.gz 的预压缩文件)
nginx.conf
http {
# 开启gzip模式
gzip on;
# 是否传输gzip压缩标志,在header中添加 Vary: Accept-Encoding
gzip_vary on;
gzip_static on;
# 压缩文件类型
gzip_types text/css application/json application/javascript application/x-javascript text/javascript font/truetype font/opentype image/webp image/svg+xml;truetype font/opentype image/webp image/svg+xml;
}
动态加载模块
const Home = lazy(() => import("./pages/Home"));
优点
- 按需加载,减少首屏加载时间
- 懒加载
- 代码拆分,减少体积
缺点
- 兼容性,现代浏览器和 node 环境原生支持
- 需要注意加载错误的情况,看情况是否需要 catch 捕获
- 切换页面资源时也需要重新加载,会出现闪烁的现象
资源压缩-[媒体文件|图片]
使用 cwebp 将图片转换为 WebP 格式
支持PNG
、JPEG
、TIFF
、WebP
转换成WebP
格式
安装压缩 webp
格式工具
brew install webp
使用
cwebp -q 80 -m 6 -noalpha ./public/img/bg/cover-high.jpeg -o ./public/img/bg/cover-high.webp
参数
参数名 | 参数值 | 说明 |
---|---|---|
-q | 0-100 | RGB 通道压缩系数 |
-m | 0-6 | 0 最小压缩 6 最大压缩 |
-noalpha | 无 | 无 alpha 通道 |
-o | string | 指定 WebP 输出文件的名称 |
-lossless | 无 | 无损压缩 |
刚开始压缩我的 jpeg 反而变大了,还是需要调整参数哈哈,而且需要考虑浏览器兼容性
Google 关于图片格式的选择指南
格式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
gif | 文件小,支持动画、透明,无兼容性问题 | 只支持 256 种颜色 | 色彩简单的 logo、icon、动图 |
jpg | 色彩丰富,文件小 | 有损压缩,反复保存图片质量下降明显 | 色彩丰富的图片/渐变图像 |
png | 无损压缩,支持透明,简单图片尺寸小 | 不支持动画,色彩丰富的图片尺寸大 | logo/icon/透明图 |
webp | 文件小,支持有损和无损压缩,支持动画、透明 | 浏览器兼容性不好 | 支持 webp 格式的 app 和 webview |
加载时机优化
script 优化
defer
异步加载/HTML 解析 => HTML 解析完成 => 执行
<script defer type="module" src="/src/main.tsx"></script>
编译后
<head>
<script type="module" crossorigin src="/assets/index-8RLgu2UJ.js"></script>
</head>
async
异步加载/HTML 解析 => 加载完成 => HTML 解析完成 => 执行
我有一个需要注入到 window 变量的 js 脚本,使用的 async 异步加载
<script async type="text/javascript" src="/ScrollSmoother.js"></script>
HTTP2
二进制分帧层
HTTP/2 所有性能增强的核心是新的二进制分帧层,它规定了 HTTP 消息的封装方式,并在客户端和服务器之间传输。
多路复用
数据流优先级
每个源一个连接
流控制
服务器推送
标头压缩
霍夫曼编码(HPACK 压缩)对标头进行压缩,并要求服务端和客户端维护一份标头索引以建立压缩上下文
nginx 开启 HTTP2
http {
server {
listen 443 ssl http2;
}
}
缓存策略
往返缓存
nginx 开启
注意:往返缓存和委托行为无法同时开启
http {
# 缓存路径
proxy_cache_path /assets levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;
server {
location / {
proxy_cache my_cache;
# 设置缓存有效时间为7天
proxy_cache_valid 200 302 7d;
# 缓存 404 响应 1 分钟
proxy_cache_valid 404 1m;
# 设置往返缓存
proxy_cache_use_stale error timeout invalid_header updating http_500 http_502 http_503 http_504;
# 允许 Nginx 在后台异步更新过期的缓存
proxy_cache_background_update on;
# 确保只有一个请求能够更新缓存,防止缓存过期时多个请求同时更新缓存,造成服务器负载过高
proxy_cache_lock on;
# 是否命中缓存
add_header X-Cached $upstream_cache_status;
}
}
}
使用 CDN 服务
目前只用了 react 等跨多页面共用资源的 CDN,自己的静态资源还没有使用 CDN 服务,目前工程体量还没有很大,delay 一下哈哈。
更多实践真实用上之后再更新…