前言
风和日丽: 我正笑嘻嘻地抓着我炫酷的ikbc键盘疯狂的敲着Bug.
晴天霹雳: 被拉进了一个群,产品说我做的网站很卡,需要做性能优化.
难以置信: 我可是用尊贵的Vue3+Ts开发的呢 (手动狗头).
十分抗拒: 迫于yin威,我给网站做了体检和手术.
体检
市面上的体检套餐有很多种,但其实都是换汤不换药.那药(标准)是什么呢?我们会在下面说明.这里我选择了谷歌亲儿子"灯塔"(LightHouse)进行性能体检.
体检标准
为什么我说大多体检套餐都是换汤不换药呢?我们先从灯塔的计分规则说起:
从上面中我们可以看到灯塔v6/v7版是通过几种性能指标及不同权重来进行计分的.这几种指标主要是根据PerformanceTiming和PerformanceEntry API标准进行定义.市面上大多体检套餐也是基于这些指标定制的.接下来我们来了解下这些指标的含义吧.
FCP (First Contentful Paint)
渲染第一个元素(文本、图片、canvas...)的时间点
SI (Speed Index)
首屏展现时间
LCP (Largest Contentful Paint)
渲染可视区域内最大内容元素的时间点
TTI (Time to Interactive)
页面资源加载成功并能响应用户交互的时间点
TBT (Total Blocking Time)
FCP到TTI之间,主线程被long task(超过50ms)阻塞的时间之和
CLS (Cumulative Layout Shift)
累计布局偏移值
FID (First Input Delay)
用户第一次在页面进行交互(点击链接、按钮、自定义js事件),到浏览器实际开始处理这个事件的时间
Core Web Vitals
谈到用户体验与性能指标,顺便提下Core Web Vitals. 2020年5月,Google针对网站使用体验推出了一套核心指标标准(Core Web Vitals).由三项指标构成:
为什么不是别的指标呢 ? 因为这套标准主要从以下三个维度进行评估:
- [加载情况] : LCP
- [交互性] : FID
- [视觉稳定性] : CLS
如何检视Core Web Vitals 指标 ?
开发者可利用以下几种工具对Core Web Vitals进行监测:
由于FID需要一个真实用户的交互,所以无法用实验数据测试.为了能在实验数据下测试FID,通常会用TBT (Total Blocking Time).虽然他们测量的内容不同,但改善TBT通常也能改善FID.
体检结果
不检不知道,一检吓一跳.6个"重要器官"凉了一半...是时候对它动个手术了!
指标评分
改善建议
手术
手术方案
既然是性能手术,方案就主要以性能指标作为维度,主要分为以下几个点:
- 视觉稳定性 (Cumulative Layout Shift)
- 加载情况 (Largest Contentful Paint)
- TTI (Time to Interactive)
- TBT (Total Blocking Time)
- FCP (First Contentful Paint)
手术过程
视觉稳定性 (Cumulative Layout Shift)
- 优化未设置尺寸的图片元素
改善建议里提到了一项优先级很高的优化就是为图片元素设置显式的宽度和高度,从而减少布局偏移和改善CLS.
<img src="hello.png" width="640" height="320" alt="Hello World" />
复制代码
- 自定义字体文件加载期间保持可见状态
改善建议里提到使用CSS font-display属性确保自定义字体文件在加载期间可见.
这是因为网站下载自定义字体文件需要一段时间,而不同浏览器此时的行为是不同的.一些浏览器在加载自定义字体时会隐藏文字,这种称之为FOIT(Flash Of Invisible Text).而一些浏览器会显示降级字体,这种情况称之为FOUT(Flash Of Unstyled Tex).这两种行为会导致"字体闪烁问题",影响视觉稳定性 (CLS).
我的处理方法是直接设置font-display:swap;这个属性能确保字体在加载时间可见.虽然还是会引发FOUT,但是相比FOIT,FOUT对视觉稳定性的影响会小一些.
更好的方案应该是预加载(preload)字体文件.让字体下载有更高概率赶在FCP之前,从而避免FOIT/FOUT.
@font-face {
font-family: 'Hello-World';
src: url('../font/Hello-World.otf') format('OpenType');
/* swap:如果设定的字体还未可用,浏览器将首先使用备用字体显示,当设定的字体加载完成后替换备用字体 */
font-display:swap;
}
复制代码
- 避免页面布局发生偏移
我们产品中有一个顶部动态插入的元素,这个元素会导致网站整体布局下移.从而造成了较大的布局偏移.跟产品及ui py交易后,我们友好地对这个元素进行了调整.将该元素脱离文档流,采用固定定位的方式进行展示.从而解决该问题.
- 避免非合成动画
改善建议中提到应避免使用非合成动画,非合成动画会使得页面变得混乱并增加CLS.关于这个优化建议我觉得应该具体场景具体分析,不应该"因噎废食".毕竟目前能被composited的css属性只有transform & opacity.当然这也在提醒我们平时在做CSS动画时应注意优化 (比如常见的使用transform替代top).
加载情况 (Largest Contentful Paint)
- 替换最大内容绘制元素
在改善建议中,我发现网站的最大内容绘制元素是谷歌地图中的一个图块元素.这也难怪LCP指标的数据表现不理想了,原因: 链路过长 - 下载谷歌地图Js sdk => 初始化谷歌地图=> 绘制 .
于是,我决定对最大内容绘制元素进行修改,从而提升LCP时间.我喵了一眼Largest Contentful Paint API 关于该元素类型的定义,将"目标"锁定到了一个loading元素 (绘制成本低: 默认渲染,不依赖任何条件和判断).经过我对该元素的尺寸动了手脚后(变大),该元素成功"上位".
TBT (Total Blocking Time) / TTI (Time to Interactive)
-
异步加载谷歌地图Js sdk
原先加载谷歌地图Js sdk是通过动态添加script标签同步加载的.这样做的缺点其实是很明显的:- Google Maps Js sdk 加载时机太晚,影响TTI表现和用户体验.
- js引擎占据主线程进行相关js执行.
我的处理方案就是对谷歌地图Js sdk进行异步加载.这里需要注意的是script async/defer的区别,我使用的是defer进行异步加载(async加载完毕后会立即执行,阻塞主线程,影响DOM解析).
<script src="//maps.googleapis.com/maps/api/js" type="text/javascript" defer ></script> 复制代码
-
优化构建bundle体积
查看基于webpack-bundle-analyzer生成的体积分析报告我发现有两个可优化的大产物:
-
- lottie动画库
站点只有一个动画效果的实现用到该库,跟产品、ui又一顿py后,我们决定牺牲一点"视觉效果".移除lottie library,改用CSS3实现.
- ant-design-vue中内置的momentjs依赖
momentjs的语言包(locale)体积非常大,而站点并无国际化需求.所以这里我直接使用webpack IgnorePlugin对语言包进行忽略.
经过优化,bundle体积(gizp前)由原来的1.8MB减小至1.3MB.
FCP (First Contentful Paint)
网站使用的是Vue做的客户端渲染.这也意味着FCP过程会有点"漫长". (初始化Vue实例等一系列工作需要占用主线程执行Js).这里我"自作聪明"地在html文件添加了"透明文本占位符",抢占FCP时间.这个骚操作我个人认为有点抖机灵,大家可以选择性无视...
<div id="app">
<!-- 占位符 -->
<p style="color:#fff;">Hello World</p>
</div>
复制代码
其他
除了针对上面几个指标维度进行优化外,我还做了几点优化,这里简单提一下:
- 优化DOM嵌套层级及数量
- 减少不必要的接口请求
- 使用translate替换top做位移/动画
手术结果
说了那么多"废话",那手术结果究竟如何呢 ? 是华丽变身还是"反向一Q日神仙"呢 ? 直接上图:
通过上图我们可以看到各项指标及评分都有质的飞跃,虽然我"不要脸地截了个最高分" (LightHouse每次评分会有波动,实际效果是由原来的50-70分涨到了70-90分) !!!
结语
平时我们侃侃而谈的性能优化点往往也是我们最容易忽略的点.性能优化也绝非一蹴而就,需要我们在日常开发中不断去发现和优化.路漫漫其修远兮...
有兴趣的可以加群:175317069 群里有公开课和自动化和性能的资料哦