本文正在参与 “性能优化实战记录”话题征文活动,
我们为什么需要性能优化?
当产品拿着手机坐在你的旁边说为什么他们的页面这么快我们怎么这么慢的时候,你就知道为什么需要做性能优化了(手动🐶)。性能优化是用户体验优化的一部分,产品的性能可以直接影响其转化率、用户留存率等指标。一句话总结就是对于页面性能我们要做到比别人更快一些。
性能优化要怎么做?
按照常规分析问题的思路:拿数据->做分析->定目标->定方案->方案实施->持续循环、总结。拿数据之前要明确我们需要哪些数据、数据如何获取,接下来就来介绍性能指标的定义与获取。
性能指标介绍
什么样的指标值得我们关注?在讨论性能的时候,精确的可量化的指标是很重要的。之前很长一段时间,我们基于 Performance API 来统计页面的domReady、DOMContentLoaded、onload等指标来衡量页面的加载性能,但这些指标不能直接反应用户视觉体验。基于这个原因,在性能指标方面我们选定加载、交互性和视觉稳定性这三个方向,来带你一起了解性能指标及其标准设定。
Google在2020年推出了一个名为 Web Vitals 的新概念,着重评估一组页面访问体验中的一部分关键指标,目的在于简化网络性能。每个指标代表页面体验的一个关键方面:加载、交互和视觉稳定性。
-
Largest contentful paint (LCP) :测量页面感知加载速度,根据页面首次开始加载的时间的时间点来报告可视区域内可见的最大图像或文本块完成渲染的相对时间 。
-
First input delay (FID): 测量从用户第一次与页面交互(例如当他们单击链接、点按按钮或使用由 JavaScript 驱动的自定义控件)直到浏览器对交互作出响应,并实际能够开始处理事件处理程序所经过的时间。(交互相应第一印象)
- 上方示例中的用户在FCP与TTI中间的3段长任务中的第2段刚开始时输入,但输入发生在浏览器正在运行任务的过程中,所以浏览器必须等到任务完成后才能对输入作出响应。浏览器必须等待的这段时间就是这位用户在该页面上体验到的 FID 值。
-
Cumulative layout shift (CLS) :测量从页面视觉稳定性,并对可见页面内容的意外布局偏移量进行了量化。 该指标突出显示用户在访问站点时遇到意外布局偏移(回流)的频率。它检查不稳定元素及其对整体体验的影响。分数越低越好。
在上方的示例中,最大的可视区域尺寸维度是高度,不稳定元素的位移距离为可视区域高度的 25%,因此距离分数为 0.25。在这个示例中变动元素占比75%即影响分数是0.75 ,距离分数是0.25 ,所以布局偏移分数是0.75 * 0.25 = 0.1875 。
其他与用户体验较为重要的指标介绍:
- First contentful paint (FCP): 测量页面开始加载到某一块内容显示在页面上的时间。(视觉第一印象)
- First Meaningful Paint (FMP): 。FMP 衡量页面的主要内容何时对用户可见。FMP 的原始分数是用户启动页面加载和页面呈现主要首屏内容之间的时间(以秒为单位)。附:阿里ARMS的FMP计算方式,原理也是利用MutationObserver Api 检测DOM增量最大 的时间点,指标已被Lighthouse废弃。
- Time to Interactive (TTI): 测量从页面加载到可视化呈现、页面初始化脚本已经加载,并且可以可靠地快速响应用户的时间。
- Total blocking time (TBT): 测量从FCP到TTI之间的时间,这个时间内主线程被阻塞的话无法响应用户输入。
- Speed Index(SI) : 速度指数, 速度指数衡量页面加载期间内容的视觉显示速度。Lighthouse 首先捕获浏览器中页面加载的视频,并计算帧之间的视觉进展。 详细介绍及使用
性能指标获取
Google官方提供了一个web-vitals库,线上或本地都可以测量上面提到的CLS、FID、LCP指标:
import {getCLS, getFID, getLCP} from 'web-vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify(metric);
// Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
(navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) ||
fetch('/analytics', {body, method: 'POST', keepalive: true});
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);
自定义模块的加载时间获取:
performance.getEntries: 该方法返回PerformanceEntry页面的所有对象的列表 。列表的成员(条目)可以通过 在明确的时间点制作性能标记或 度量(例如通过调用mark()方法)来创建。如果您只对某些类型或具有某些名称的性能条目感兴趣,请参阅getEntriesByType()和getEntriesByName()。
function use_PerformanceEntry_methods() {
console.log("PerformanceEntry tests ...");
if (performance.mark === undefined) {
console.log("... performance.mark Not supported");
return;
}
// Create some performance entries via the mark() method
performance.mark("Begin");
do_work(50000);
performance.mark("End");
performance.mark("Begin");
do_work(100000);
performance.mark("End");
do_work(200000);
performance.mark("End");
// Use getEntries() to iterate through the each entry
let p = performance.getEntries();
for (var i=0; i < p.length; i++) {
console.log("Entry[" + i + "]");
check_PerformanceEntry(p[i]);
}
// Use getEntriesByType() to get all "mark" entries
p = performance.getEntriesByType("mark");
for (let i=0; i < p.length; i++) {
console.log ("Mark only entry[" + i + "]: name = " + p[i].name +
"; startTime = " + p[i].startTime +
"; duration = " + p[i].duration);
}
// Use getEntriesByName() to get all "mark" entries named "Begin"
p = performance.getEntriesByName("Begin", "mark");
for (let i=0; i < p.length; i++) {
console.log ("Mark and Begin entry[" + i + "]: name = " + p[i].name +
"; startTime = " + p[i].startTime +
"; duration = " + p[i].duration);
}
}
其他指标获取方案:
线上测量工具
- Chrome User Experience Report
- PageSpeed Insights
- Search Console (Core Web Vitals report)
- web-vitals JavaScript library
- sitespeed.io
实验室工具
- Chrome DevTools:Network可观察网络资源加载耗时及顺序,Performace可观察页面渲染表现及JS执行情况
- Lighthouse:对网站进行整体评分,找出可优化项
- WebPageTest
指标数据分析
- 使用chrome的lighthouse查看对应页面的实验室指标,对比web.dev/speed-index…和www.dianping.com/
优化目标制定
优化一定是有目标的,不能说一顿操作下来再去查看对应的性能指标发现是负优化。研究哪些指标对您的应用程序最重要:通常,它将由您开始渲染界面最重要像素的速度以及为这些渲染像素提供输入响应的速度来定义。这些将为您提供持续努力的最佳优化目标。与其关注整个页面的加载时间(例如 onLoad 和 DOMContentLoaded 时间),不如优先考虑用户感知到的页面加载时间。业界目标一般就是较优化前:FCP降低至x毫秒、LCP降低至x毫秒、SI指标降低至x毫秒,将Lighthouse对应的Perfomance分数提高至x分。
优化方案调研、实施
目标有了,那么就要找到影响对应指标的因子、优化对应指标的方案。对应 Lighthouse 各重要指标,Lighthouse插件内也有提供对应的影响因子。按照这些影响因子去调研方案、进行方案的可行性分析,然后按照方案的性价比进行优先级排序。下面我们对部分优化方案进行介绍,像一些已经广为熟知的方案不再陈述,如:gzip压缩、减少HTTP请求、压缩css/js代码。方案点到为止,详细内容可以点击标题进行查看。
1.适当的服务端渲染替代客户端渲染
- SSR:服务器端渲染 - 在服务器上将客户端或通用应用程序渲染为 HTML。
- CSR:客户端渲染 - 在浏览器中渲染应用程序,通常使用 DOM。
- Rehydration:在客户端“启动”JavaScript 视图,以便它们重用服务器渲染的 HTML 的 DOM 树和数据。
- Prerendering:在构建时运行客户端应用程序以将其初始状态捕获为静态 HTML。
如上图,SSR会减少 FCP 的时间,但是会增加 TTFB 时间。当然合适的SSR方案比如[流式渲染]整体而言会提高LCP 。推荐阅读:rendering-on-the-web
2.使用 PRPL 模式加载页面
PRPL 是首字母缩写词,一种让网页加载变得可交互、更快速的模式。
- Push (或者 preload): 给用户推送最重要的资源
- Render: 尽快的渲染最初始的路由
- Pre-cache: 提前缓存剩余的资源
- Lazy load: 懒加载其他路由或者次要资源
3.优化API的性能, BFF
在有较多展示模块信息的页面被访问的时候通常情况会发送较多的请求条数,如果能将这些接口进行聚合或按首屏/非首屏进行自行组合是可以提高页面的性能指标(秒开率)。
4.使用并合适的配置CDN
- 使用CDN: CDN快速交付资源,减少源站负载,有助于应对流量高峰。
- 尽可能地缓存内容: 静态和动态内容都可以而且应该缓存 - 尽管持续时间不同。定期审核您的网站以确保您以最佳方式缓存内容。
- 启用 CDN 性能特性: Brotli压缩、TLS 1.3、HTTP/2 和 HTTP/3 等特性进一步提高了性能。
5.静态资源优化
-
使用 Brotli 进行纯文本(html、css、js等非图片资源)压缩
-
使用image-set设置响应式的背景图
background-image: url("fallback.jpg"); background-image: image-set( "photo-small.jpg" 1x, "photo-large.jpg" 2x, "photo-print.jpg" 600dpi); -
对于照片,我们使用与picture元素进行图片协商的方法。AVIF 格式 > WebP > JPEG / PNG 。当然真正编写代码的时候无需像下面这么麻烦,封装组件使用即可。查看图片格式优劣对比详情。
<picture> <source sizes="(max-width: 608px) 100vw, 608px" srcset=" /img/Z1s3TKV-1920w.avif 1920w, /img/Z1s3TKV-1280w.avif 1280w, /img/Z1s3TKV-640w.avif 640w, /img/Z1s3TKV-320w.avif 320w" type="image/avif" /> <source sizes="(max-width: 608px) 100vw, 608px" srcset=" /img/Z1s3TKV-1920w.webp 1920w, /img/Z1s3TKV-1280w.webp 1280w, /img/Z1s3TKV-640w.webp 640w, /img/Z1s3TKV-320w.webp 320w" type="image/webp" /> <source sizes="(max-width: 608px) 100vw, 608px" srcset=" /img/Z1s3TKV-1920w.jpg 1920w, /img/Z1s3TKV-1280w.jpg 1280w, /img/Z1s3TKV-640w.jpg 640w, /img/Z1s3TKV-320w.jpg 320w" type="image/jpeg" /> <img src="fallback-image.jpg" alt="Photo" width="450" height="350"> </picture> -
通过根据媒体查询指定不同的图像显示尺寸来使用 Sizes 属性替换。
-
使用 Intersection Observer 延迟加载图像和视频 (lazy load)。
-
使用CSS新属性 content-visibility 延迟渲染元素内容。点击查看更多
6.构建优化(webpack)
-
减少打包时间:
- 缩减范围:配置include/exclude缩小Loader对文件的搜索范围。
- Loader缓存:配置cache缓存Loader对文件编译的结果。
- 预构建:配置 DllPlugin 将第三方依赖提前打包。
- 并行构建:配置 thread-loader,开启多线程构建。
-
减少打包体积:
- 分割代码:使用 splitChunks 拆分构建包,分割各个模块代码,提取相同部分代码,好处是减少重复代码的出现频率。
- 摇树优化:使用 treeShaking,移除重复代码和未使用代码。
- 动态垫片:使用 html-webpack-tags-plugin 根据UA返回当前浏览器代码垫片,好处是无需将繁重的代码垫片打包进去。
- 按需加载:将路由页面/触发性功能单独打包为一个文件,使用时才加载,好处是减轻首屏渲染的负担
总结
性能优化不是这么短的时间就能全部讲完,优化手段也随着技术的升级在持续的变化,所以本文能到给大家更多的是一个方向一种态度,希望大家在编码的同时考虑到性能对用户的影响更加注意页面性能的优化。