我们在进行前端项目的优化时,经常会需要优化页面的加载性能,在 window 中有着
DOMContentLoaded
和 load
事件,分别代表 DOM 构造完成和首屏资源加载完成。但是在我们使用 UI 框架来构造 WEB 应用时,都是通过 JS 来操作 DOM 完成页面的构建,DOMContentLoaded
和 load
事件并不能准确地衡量应用的首屏时间。我们打开 Chrome 的控制台时可以看到性能一栏,使用这个功能检测页面的加载过程,可以发现几个阶段,依次是:FP,FCP,DCL,L
性能指标事件
- FP (First Paint):渲染出第一个像素点,一般在 HTML 全部解析或解析一部分时触发。
- FCP (First Contentful Paint):渲染出第一个内容,可以是文本,图片或 canvas。
- DCL(DOMContentLoaded):DOM 解析完成,style 和 图片 等可能还处于加载状态,对应
DOMContentLoaded
事件,document.onload
和document.body.onload
。
- LCP(largest contentful Paint):页面可视区域内可见的最大的片或文本快完成渲染的时间。
- L(load):页面中所有资源都被加载完成(包括异步加载,但不包括延时加载的资源,如图片的 lazy),即
window.onload()
事件。
加载相对事件
- 白屏事件:从输入网址到浏览器出现第一个元素,即 FP 事件。
- 首屏事件:从输入地址到浏览器第一屏渲染完成,即 FCP 事件。
FCP 事件代表第一个内容被渲染出来,如果需要更关键的内容标志首屏,可以采用 LCP 事件。
交互事件
- FID(First Input Delay):用户第一次与网站交互(如单击链接或按钮,或操作一些自定义控件)时到浏览器对交互做出响应经过的时间。
- TTI(Time to interactive):页面开始加载到完成渲染,并且所有可见元素的关联事件的响应函数都注册完成,事件响应函数可以在触发后 50ms 内执行(无 longtask 阻塞)。
- longTask:阻塞线程超过 50ms 或以上的任务
监听页面加载事件
可以通过 PerformanceTiming API
通过 web-vitals 获取
关于埋点接口的几个问题:
1. 为什么会采用 1x1 的透明 gif 来传递指标
因为埋点服务器一般为第三方,存在跨域问题。而 1x1 的透明 gif 在支持跨域的格式中最小。
2. 埋点采用的请求方式
navigator.sendBeacon()
,这个方法可用于通过 HTTP 将少量数据异步传输到 Web 服务器 而不占用进程
3. 埋点还可以放在 worker 线程中React 优化首屏事件
对于 React 应用来说,FP 和 FCP 事件都在 DCL 事件之后。所以我们的目标是让 mian.js 尽快执行。我们可以从文件结构和文件大小两方面来优化:
文件结构
这部分是 Webpack 打包时就自动做好的,在 HTML 中引入的 mian chunk 被自动加上了 defer。
- 减少 DOM 树的内容和层级,减少 DOM 的解析和渲染时间。
- 将 CSS 放在页面开始位置,来让 CSSOM 尽快加载和解析。
- 将 JS 放在页面底部位置,使用 async 或 defer 避免 JS 阻塞 DOM。
文件大小
- 打包时对 JS 文件进行 uglify。
- 项目内的组件和路由页面采用动态引入,减少 main chunk 的大小。
- 打包生成
.gz
文件,nginx
开启gzip
。
- 使用
webpack-bundle-analyzer
对项目进行依赖分析,对较大的包采用动态引入或者 CDN 引入。对于 lodash 可以采用引入子包的方法。
- 开启 tree shaking 移除无用代码。
采用 Next.js
使用 Next 无论是 静态加载还是服务端渲染,都会预先返回 DOM 结构,不用等待全部的逻辑代码,所以 Next.js 的首屏事件在 DCL 事件之前。