1. 6大测量指标优化策略排序
- Largest Contentful Paint 【简称LCP: 最大内容渲染】 FCP最大内容渲染时间标记了渲染出最大文本或图片的时间
- Total Blocking Time 【简称TBT: 总阻塞时间】 TBT测量了FCP(首次内容渲染)和TTI(可交互时间)之间的总耗时。TTI可能会被主线程阻塞以至于无法及时响应用户。大于50ms的任务称为长任务,当任意长任务出现时,主线程则称为被阻塞状态。由于浏览器不会打断正在进行中的长任务,所以,如果用户在执行长任务时和页面有交互事件时,浏览器必须等到该长任务完成才能响应。TBT计算的是在FCP到TTI之间所有长任务时间内总和。
如下图所示,我们有5个任务,其中有3个是长任务,因为它们的时长超过了50ms。 虽然运行任务的总时长是560ms但是只有345ms被认为是总阻塞时长。
- First Contentful Paint 【简称FCP: 首次内容渲染】 FCP测量了从页面开始加载到页面任意部分的内容渲染到屏幕上。
- Speed Index 【简称SI: 速度指数】 SI速度指数表明了网页内容的可见填充速度。lighthouse首先捕获页面加载的视屏,然后对比帧与帧之间视觉效果变化(通过计算结构相似指数SSMI来比较)。 如何提升SI呢?
- 最小化主线程工作量
- 减小JS的执行时间
- 确保在字体加载过程中文本可见
- Time to Interactive 【简称TTI: 可交互时间】 可交互时间是指网页需要多长时间才能提供完整交互功能。TTI测量了从页面开始加载到页面的主要附属资源加载完毕,并且可以足够快速回应用户输入的所用时间。
- Cumulative Layout Shift 【简称CLS: 累积布局偏移】 CLS累积布局偏移旨在测量可见元素在视口内的移动情况。CLS值越小越好。
2. 针对各个测量指标的优化措施
措施1:优化LCP
优化弹出框,替换过大背景图片。对图片做preload。加快图片下载。
措施2:减少无用JS、CSS,减小TBT,提升SI
- 彻底code split
- 前端路由懒加载
- split chunk(比如antd,antd-mobile等)
- 微前端预加载
- 借助Webpack Bundle Analyzer帮助我们分析项目
措施3:首图图片减小自定义首屏时间
- 缺省页svg转base64
- base64走磁盘缓存
措施4:资源预加载
这里可以使用webpack插件来打包时增加preload属性
<!-- preload js -->
<link rel="preload" as="script" href="{js地址}" crossorigin />
<!-- preload css -->
<link rel="preload" as="style" href="{css地址}" crossorigin />
<!-- preload 图片 -->
<link rel="preload" as="image" href="{图片地址}" crossorigin />
// webpack
new PreloadWebpackPlugin({
rel:"preload",
as(entry){
if(/\.css$/.test(entry)) return "style"
if(/\.woff$/.test(entry)) return "font"
if(/\.png$/.test(entry)) return "image"
return "script"
}
})
措施5:提前建立第三方链接
注意:只对一些立即用到的第三方站点做这种preconnect处理,要省着用preconnect。 用preconnect给所有的第三方站点都做提前链接很低效,可以为重要的第三方站点做preconnect,其余的都做dns-prefetch. 可节约20-120ms。
<!-- preconnect 3rd party -->
<link rel="preconnect" href="https://example.com">
<link rel="dns-prefetch" href="https://www.google-analytics.com" />
<link rel="dns-prefetch" href="https://cm.g.doubleclick.net" />
措施6:减少CLS
其实布局有偏移还是很常见的。比如我们的用户信息数据,在我的页面的用户信息卡片里,是异步接口获取的。所以看到可能会是页面先加载了一部分,然后拿到数据后进度条显示并生成进度条。这种异步的内容减少CLS的方式:
可以想办法把这个异步接口合并到主接口里来解决,或者设置一个默认的图片占位。等数据来了再替换掉。 useSWR:异步更新缓存策略,可参考深入浅出 useSWR 原理 - 知乎 (zhihu.com)
措施7:浏览器缓存机制
结合强缓存和协商缓存,合理地缓存静态资源,比如当我们的资源内容不可复用时,直接为 Cache-Control 设置 no-store,拒绝一切形式的缓存;否则考虑是否每次都需要向服务器进行缓存有效确认,如果需要,那么设 Cache-Control 的值为 no-cache;否则考虑该资源是否可以被代理服务器缓存,根据其结果决定是设置为 private 还是 public;然后考虑该资源的过期时间,设置对应的 max-age 和 s-maxage 值;最后,配置协商缓存需要用到的 Etag、Last-Modified 等参数。
其他措施
当把lighthouse给出的opportunities和diagnosis都做到了以后,可考虑
减少服务端TTFB,需要后端优化主接口。 第三方脚本治理:能优化就优化,不能优化的想办法置后加载,避免阻塞主js。
代码中的优化
“%” 取余运算,“/” 取模运算
前端性能指标
-
redirectStart:第一个HTTP重定向开始的时间戳。如果没有重定向,则此值为0。
-
redirectEnd:最后一个HTTP重定向完成(即接收到最后一个重定向响应的最后一个字节)的时间戳。如果没有重定向,则此值为0。
-
fetchStart:浏览器准备好使用HTTP请求来获取文档的时间戳。这个时间点会在检查任何应用缓存之前。
-
domainLookupStart:域名查询开始的时间戳。如果使用了持续连接(keep-alive connection)或缓存,则此值为0。
-
domainLookupEnd:域名查询完成的时间戳。
-
connectStart:浏览器与服务器建立连接以获取文档的时间戳。如果文档是从缓存中获取的,则此值为0。
-
secureConnectionStart:HTTPS握手开始的时间戳。如果页面是通过HTTP加载的,则此值为0。
-
connectEnd:浏览器与服务器之间的连接建立完成的时间戳。如果文档是从缓存中获取的,则此值为0。
-
requestStart:浏览器开始请求文档的时间戳。这个时间点会在发送HTTP请求的头部之后。
-
responseStart:浏览器从服务器接收到第一个字节的时间戳。
-
responseEnd:浏览器从服务器接收到最后一个字节(即响应完成)的时间戳。
-
domLoading:开始解析文档的时间戳(并非所有浏览器都直接提供此属性,但它是解析过程的开始)。
-
domInteractive:解析器完成了解析文档并且文档已经准备好与JavaScript交互的时间戳(即DOM完全加载和解析完成,但样式表、图片和子框架可能仍在加载)。
-
domContentLoadedEventStart:DOMContentLoaded事件触发的时间戳,表示HTML文档解析完成并且所有脚本文件已下载完成。
-
domContentLoadedEventEnd:DOMContentLoaded事件结束的时间戳,表示所有脚本文件已执行完成。
-
domComplete:文档和所有子资源(例如图片、样式表等)都已完成加载的时间戳。
-
loadEventStart:load事件开始的时间戳,表示所有资源(包括图片、样式表、脚本文件等)都已加载完成。
-
loadEventEnd:load事件结束的时间戳,表示所有资源(包括图片、样式表、脚本文件等)都已执行完成。
-
FCP(First Contentful Paint):首次内容绘制时间,即浏览器首次绘制任何文本、图像、非空白canvas或svg的时间
-
SI(Speed Index):速度指数,即页面渲染速度的指标
-
LCP(Largest Contentful Paint) :最大内容绘制时间,即页面中最大的可见内容元素绘制完成时间
-
TBT(Total Blocking Time): 总阻塞时间,即页面主线程被阻塞的总时间
-
TTI (Time to Interactive):TTI 是指页面变得可交互所需的时间
-
TTD(Time to Display): TTD 是指页面显示所需的时间
FCP performance.getEntriesByType('navigation')[0].responseStart
SI let navigation = performance.getEntriesByType('navigation')[0].toJSON(); let si = navigation['loadEventEnd'] - navigation['domContentLoadedEventEnd']
LCP const [performanceData] = performance.getEntriesByType("navigation"); let lcp = performanceData.largestContentfulPaint;
TBT let navigationEntries = performance.getEntriesByType('navigation'); let navigationEntry = navigationEntries[0]; let navigationEntryJson = navigationEntry.toJSON(); let tbt = navigationEntryJson['totalBlockingTime'];
TTI const [performanceData] = performance.getEntriesByType("navigation"); if (navigationEntries.length > 0) { const interactiveTime = performanceData.domInteractive - performanceData.navigationStart; const loadTime = performanceData.loadEventEnd - performanceData.navigationStart; const tti = interactiveTime + loadTime; console.log('TTI:', tti); }
TTD
const [performanceData] = performance.getEntriesByType("navigation"); const ttd = performanceData.domInteractive - performanceData.requestStart;
白屏时间 // 计算白屏时间 (当前的) var whiteScreenTime = performanceData.domInteractive - performanceData.responseStart;
监控
接入监控流程
- 应用项目接入监控,上送数据
- 后端进行数据存储和分析
- 在数据监控平台上显示各个监控平台的报警信息(根据各数据的配置)
错误类型
- 页面错误,页面异常,导致页面白屏
- 网络错误,服务端异常导致的错误,或者不符合前后端约束的错误
错误收集
- try/catch 捕捉同步错误,不能捕获异步错误
- window.addEventListener('error') , window.onerror ,监听JS运行错误,和静态资源加载错误
- react 错误边界 componentDidCatch
- window.addEventListener('unhandledrejection') j监听异步报错
- 网页崩溃方案处理,通过service worker 发送心跳检测
数据收集
- 错误收集(JS运行错误,静态资源加载错误,应用崩溃错误,服务网络错误)
- 基础信息收集(设备信息收集,运行环境信息收集,用户信息收集)
- 用户行为信息收集(点击行为,关键链路)
- 性能监控 onload中,获取性能参数,PerformanceObserver