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个"重要器官"凉了一半...是时候对它动个手术了!
指标评分
market-optimize-before.png
改善建议
improve-advice.png
手术
--
手术方案
既然是性能手术,方案就主要以性能指标作为维度,主要分为以下几个点:
-
视觉稳定性 (Cumulative Layout Shift)
-
加载情况 (Largest Contentful Paint)
-
TTI (Time to Interactive)
-
TBT (Total Blocking Time)
-
FCP (First Contentful Paint)
手术过程
视觉稳定性 (Cumulative Layout Shift)
- 优化未设置尺寸的图片元素
改善建议里提到了一项优先级很高的优化就是为图片元素设置显式的宽度和高度,从而减少布局偏移和改善CLS.
image-size.png
<img src="hello.png" width="640" height="320" alt="Hello World" />
- 自定义字体文件加载期间保持可见状态
改善建议里提到使用CSS font-display属性确保自定义字体文件在加载期间可见.
webfont-load.png
这是因为网站下载自定义字体文件需要一段时间,而不同浏览器此时的行为是不同的.一些浏览器在加载自定义字体时会隐藏文字,这种称之为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;
}
- 避免页面布局发生偏移
cls-ele.png
我们产品中有一个顶部动态插入的元素,这个元素会导致网站整体布局下移.从而造成了较大的布局偏移.跟产品及ui py交易后,我们友好地对这个元素进行了调整.将该元素脱离文档流,采用固定定位的方式进行展示.从而解决该问题.
- 避免非合成动画
non-composited-animation.png
改善建议中提到应避免使用非合成动画,非合成动画会使得页面变得混乱并增加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标签同步加载的.这样做的缺点其实是很明显的:
我的处理方案就是对谷歌地图Js sdk进行异步加载.这里需要注意的是script async/defer的区别,我使用的是defer进行异步加载(async加载完毕后会立即执行,阻塞主线程,影响DOM解析).
-
Google Maps Js sdk 加载时机太晚,影响TTI表现和用户体验.
-
js引擎占据主线程进行相关js执行.
-
优化构建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
其他
除了针对上面几个指标维度进行优化外,我还做了几点优化,这里简单提一下:
-
优化DOM嵌套层级及数量
-
减少不必要的接口请求
最后
javascript是前端必要掌握的真正算得上是编程语言的语言,学会灵活运用javascript,将对以后学习工作有非常大的帮助。掌握它最重要的首先是学习好基础知识,而后通过不断的实战来提升我们的编程技巧和逻辑思维。这一块学习是持续的,直到我们真正掌握它并且能够灵活运用它。如果最开始学习一两遍之后,发现暂时没有提升的空间,我们可以暂时放一放。继续下面的学习,javascript贯穿我们前端工作中,在之后的学习实现里也会遇到和锻炼到。真正学习起来并不难理解,关键是灵活运用。