由于 FID
需要一个真实用户的交互,所以无法用实验数据测试。
为了在实验数据下预测 FID
,通常会用 TBT(Total Blocking Time)
,具体这个指标后面文章会介绍。他们测量的内容不同,但改善 TBT
通常也能改善 FID
。
一个糟糕的 FID
主要原因是JS执行过长,优化JS的解析、编译、执行可以直接降低 FID
。
过长的JS执行
当JS执行过程中,浏览器无法响应用户交互,因为主线程被占用,为了改善这点,可以:
- 分解长任务
- 优化页面,为交互准备
- 使用 Web Worker
- 减少JS执行时间
分解长任务
如果你准备尝试减少单个页面的js的体积,可以先考虑把较长执行的js代码分解成小的异步任务。
长任务指的是用户可能会发现页面无响应的时期执行的js代码。任何阻塞主线程大于等于50ms的代码都是长任务。长任务一般是js体积过大的潜在因素(浏览器加载并执行了比页面初始化所需要的更多的js)。
分解长任务可以降低用户输入延迟。
当你采用最佳实践(例如拆分代码、分解长任务),FID
会有显著改善。虽然 TBT
并非现场数据指标,但这对于改善 FID
和 TTI(Time To Interactive)
都很有帮助。
优化页面,为交互准备
造成 FID
和 TBT
分数低有很多原因,大多都是js引起的。
自己站点的脚本执行可能会延后交互
- JS体积过大,执行时间过长,无效的分包会导致页面响应用户交互变慢,影响
FID
、TBT
、TTI
。逐步加载代码和功能块可以拆分这些任务,提升响应速度。 - 服务端渲染看上去页面是出来了,但用户的交互还是受限于js的执行时间,可以考虑把更多逻辑代码放在服务端实现,或者在构建的时候创建更多静态内容。
下图是 TBT
得分的优化前后对比。通过将非必须的昂贵的脚本的加载和执行移出关键路径,用户就可以更快的去与页面交互。
数据获取会影响交互准备的很多方面
- 级联的获取数据的水流图(包含js,数据的网络请求等),会导致交互延迟。目的是要减少对级联数据获取的依赖。(减少请求数)
- 较大的内联数据可以节省HTML的解析时间,同时影响绘制图像和交互两种指标。目的是要减少客户端后续处理对数据的依赖。(数据在内联已经准备好了,不需要额外请求)
第三方脚本的执行可能会延后交互
- 很多网站都包含第三方库的标签和统计代码,这些会导致网络阻塞,使得主线程长时间无法响应,延后了交互。查找出必须加载的第三方代码。(例如:不滚动到指定位置不展示广告)
- 有时候,第三方的脚本会抢先于本站脚本加载,例如加载优先级和带宽限制。尝试着优先加载你觉得可以给用户提供最有价值的东西。
使用 web worker
主线程阻塞是导致输入延迟的主要因素之一。web worker可以让你的代码在后台进程中执行,把一些非UI的操作放在web worker中执行可以减少主线程压力,改善 FID
指标。
可以使用以下的库,让你的站点更方便的集成web worker:
- Comlink
- Workway
- Workerize
减少JS执行时间
- 延后加载未使用的js
- 最小化无用的polyfill
延后加载未使用的js
通过开发者工具中的coverage的tab页可以查看各资源的有效使用率。
为了减少无用JS,可以:
- 把你的代码拆分成多个chunk,按需加载
- 使用
async
或者defer
延后加载非关键脚本,包含第三方脚本
代码拆分指的是将一个大的单个JS拆分成多个小的,根据条件去加载对应的JS。现代浏览器已经支持按需加载:
import('module.js') .then((module) => { // Do something with the module. });
除了常用浏览器支持以外,一些构建系统也支持:
- webpack,rollup,parcel等构建工具
- angular,react,vue等客户端框架
除了可以使用代码拆分,也可以使用 async
或者 defer
来延后加载非关键脚本。
<script defer src="…"></script>
<script async src="…"></script>
除非有特殊原因,一般第三方脚本都可以默认采用这种方式加载。
最小化无用的polyfill
如果你用了一些js高级语法,你可能需要将这些代码转换成旧版浏览器支持的语法,或者引入polyfill来支持。
最好的做法是,如果浏览器支持这些语法,不引入polyfill。最小化无用的polyfill,并且将它们的使用限制在需要它们的环境中,可以降低js的体积。
优化polyfill的使用,可以:
- 如果你是用babel转义,使用
@babel/preset-env
可以只包含你需要支持的浏览器的polyfill。对于babel 7.9,可以开启bugfixes
配置,进一步减少无用的polyfill。 - 使用
module/nomodule
的模式传输两份不同的bundle。(@babel/preset-env
也支持,可以通过target.esmodules
)
<script type="module" src="modern.js"></script>
<script nomodule src="legacy.js" defer></script>
这样可以保证支持js模块的浏览器,可以加载模块化的打包文件,不支持的浏览器可以加载转义后的打包文件。
开发者工具
Lighthouse 6.0 不能测试 FID
,因为它是一个现场数据指标,但是 TBT
可以作为替代品测试。针对 TBT
的优化项对 FID
也同样有效。
总结
实际项目的优化需要频繁的使用开发者工具 performance 和 lighthouse。针对长任务进行拆解,针对未使用的js进行移除,针对复杂的js使用web worker。最后再针对旧版浏览器和新版浏览器加载不同资源,以保证新版浏览器的对polyfill更少的依赖。如果使用webpack打包的项目,可以查看打包的分布图,针对性的去优化每一个bundle。
参考
https://web.dev/optimize-fid/