Vue首屏时间指标采集最佳方式详解

作者:皮皮大人 时间:2024-06-05 09:17:24 

前言

SPA项目中,首屏加载速度都是老生常谈的问题了,首屏时间直接反应了用户多久能看到页面的主要内容,这决定了用户体验,本文聊一聊如何采集首屏时间,本文主要是单指正常记录首屏时间(不和首屏js资源报错等等挂钩)

Performance

timing

  • connectStart:HTTP域名解析完成的时间

  • connectEnd:HTTP浏览器与服务器之间连接建立完成的时间

  • domComplete:DOM文档解析完成,readyState变为complete

  • domContentLoadedEventStart:所有脚本已经执行完,开始执行DOMContentLoaded方法

  • domContentLoadedEventEnd:执行DOMContentLoaded方法结束

  • domInteractive:DOM结构加载结束,开始加载内嵌资源,readyState变为interactive

  • domLoading:DOM结构开始解析,readyState开始是loading

  • domainLookupStart:DNS域名查询开始

  • domainLookupEnd:DNS域名查询结束

  • fetchStart:浏览器发起任何请求之前的时间戳

  • loadEventStart:开始加载load事件

  • loadEventEnd:load事件加载结束

  • navigationStart:unload上一个文档的时间节点

  • redirectStart:第一个页面重定向开始的时间

  • redirectEnd:最后一个页面重定向结束的时间

  • requestStart:浏览器向服务器发起HTTP请求(包含缓存,本地资源)

  • responseStart:浏览器从服务器收到HTTP请求返回的第一个字节的时间

  • responseEnd:浏览器从服务器收到HTTP请求返回的最后一个字节的时间

  • secureConnectionStart:HTTPS协议握手之前的时间,如果非HTTPS,则为0

  • unloadEventStart:上一个文档unload事件的开始时间(需要是同源文档,否则为0)

  • unloadEventEnd:上一个文档unload事件的结束时间(需要是同源文档,否则为0)

那么首屏的时间是不是可以简单取值为:

domComplete - navigationStart

答案是不可以的,因为在Vue和React等SPA框架中,页面是空的,需要加载js,然后通过js脚本来把页面内容渲染出

来,所以上面简单的运算是得不到真正首屏时间的

自动化采集和思考

手动化采集侵入代码性强,而且也无法一劳永逸,可能也导致数据不够标准,所以这里我采用的方式自动化采集,就是用一段代码来做首屏的自动化采集。这里思考热门方式:

MutationObserver  监听根节点的 dom 节点数量

当然还有个方案,计算计算FMP 如何相对准确的计算 FMP (当然我这里没有使用该文章的方式,因为觉得执行起来太过复杂)

此 API 监听页面 DOM 变化,并告诉我们每次变化的 DOM 是被增加还是删除

MutationObserver缺点: 无法兼容骨架屏有无的情况,如果页面有骨架屏,也没法真正检测出真正的白屏时间

而且难以决定什么是加载页面完成的标记

我的方案

实现:

其实我的思考的方案很简单,核心代码其实只有两行,就是通过 Vue.mixin() 混入组件 mounted 的时间,然后统计每个组件的加载在页面上的时间,最后一个组件加载的时间就是用户看到的首屏时间(因为所有组件已经加载完毕,不包括异步组件)

import Vue from 'vue';
class whiteScreen {
   constructor() {
       const timing = window.performance.timing;
       // 记录开始时间
       this.startTime = timing.navigationStart || timing.connectStart ||
       dayjs().format('YYYY-MM-DD HH:mm:ss:SSS');
       // 加载中状态
       this.loading = false;
       // 收集每个组件加载的完成数据
       this.times = [];
       // 记录组件是否加载过,因为一个页面会多次用到某组件,只记录第一次加载成功即可
       this.isLoadedComp = {};
       // 是否在加载中
       this.setLoading(true);
       // 利用vue的mixin记录每个组件挂载完成的时间
       const _this = this;
       Vue.mixin({
           /**
           * 注意这里要用到mounted而不是created,因为我们要记录白屏的时间
           * 所以是用户看到界面的时刻,用mounted比created更加适合,具体看 vue 组件的生命周期
           * 另外vue在组件和子组件加载机制。created和mounted执行时机也存在区别
           */
           mounted() {
           // 如果不是正在加载中,则返回
           if (!_this.isLoading()) return;
           // 获取组件标签
           const name = this.$options.name || this.$options._componentTag;
           // 如果该组件已经加载过,则不用再记录
           if (_this.isCompLoaded(name)) return;
           this.$nextTick(() => {
               if (name) {
                   _this.push({
                   name: name,
                   // 记录当前组件加载成功的时间
                   time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
            });
           }
          });
         }
     });
   }
   isLoading() {
       return this.loading;
   }
   setLoading(value) {
     this.loading = value;
     if (!this.isLoading()) {
       const data = [...this.times];
       const startTime = this.startTime;
       // TODO: 上传埋点
       console.log({
         data,
         startTime,
       });
     }
   }
   isCompLoaded(name) {
     if (!this.isLoadedComp[name]) {
       this.isLoadedComp[name] = true;
       return false;
     }
     return this.isLoadedComp[name];
   }
}

解释一下上述代码,如上面所说的,用了Vue.mixin的方式记录每个组件的加载的完成时间,上面有个对象 isLoadedComp 用来记录页面是否加载过组件,举个例子说明:

page含有A组件,但是A组件在page有被多次使用到,所以我们只需要第一次加载作为依据即可

使用了这段代码后,我们就会得到这样如下的 data 数据结构,如下图:

Vue首屏时间指标采集最佳方式详解

这样我们准确的取得了页面加载每个组件用到的时间,但是还存在一个问题,上面的 loading 状态应该何时结束,我们要根据什么作为页面加载完成的依据,这里大家不妨思考下

设置组件最大加载时间

来看看这种情况,页面 page 有异步组件的情况,page有10个组件,2个组件是异步,8个同步组件,加载同步组件需要2面,加载异步组件需要10秒,理论上我们的白屏时间应该2s,而不是8秒,因为此时用户已经能看到界面,并且可以做一些有效点击操作,所以我们结合上面的,什么作为页面加载完成的依据得出我们的设计方式

这样我们可以设置一个组件最大的加载时间,用一个倒计时,每次组件加载完就清空倒计时,再重新创建倒计时。如果加载时间超过倒计时的时间,则这个组件不是首屏的时间计算之内。

什么作为页面加载完成的依据?倒计时结束就不再获取组件的加载完成时间,得出来的页面最后加载的组件的时间就是首屏结束的时间

上代码:

import Vue from 'vue';
class whiteScreen {
   constructor({ safeTime = 3000 } = {}) {
       // 设置组件最大加载时间
       this.safeTime = safeTime;
       // 其他...
       Vue.mixin({
           /**
           * 注意这里要用到mounted而不是created,因为我们要记录白屏的时间
           * 所以是用户看到界面的时刻,用mounted比created更加适合,具体看 vue 组件的生命周期
           * 另外vue在组件和子组件加载机制。created和mounted执行时机也存在区别
           */
           mounted() {
           // 如果不是正在加载中,则返回
           if (!_this.isLoading()) return;
           // 获取组件标签
           const name = this.$options.name || this.$options._componentTag;
           // 如果该组件已经加载过,则不用再记录
           if (_this.isCompLoaded(name)) return;
           this.$nextTick(() => {
               if (name) {
                   _this.push({
                   name: name,
                   // 记录当前组件加载成功的时间
                   time: dayjs().format('YYYY-MM-DD HH:mm:ss'),
            });
           }
          });
         }
     });
   }
   isLoading() {
       return this.loading;
   }
   setLoading(value) {
     this.loading = value;
     if (!this.isLoading()) {
       const data = [...this.times];
       const startTime = this.startTime;
       // TODO: 上传埋点
       console.log({
         data,
         startTime,
       });
     }
   }
   createCountDown() {
     window.clearTimeout(this.countTime);
     this.countTime = window.setTimeout(() => {
       this.setLoading(false);
     }, this.safeTime);
   }
   isCompLoaded(name) {
     if (!this.isLoadedComp[name]) {
       this.isLoadedComp[name] = true;
       return false;
     }
     return this.isLoadedComp[name];
   }
   push(item) {
     // 重新创建定时器
     this.createCountDown();
     this.times.push(item);
   }
}

这种方式是存在一定缺陷,虽然 safeTime 是可以传进来的,但是这个值不好设置,这里我们默认3秒,如果组件需要加载3秒,或者3秒内没有组件加载,我们视为首屏加载结束(注意:这里的倒计时不是从 window.onload 开始的,而且在第一个组件mounted完成的时候开始,所以算的组件加载完成到下一个组件加载完成是否超过3秒)

怎么兼容骨架屏完成的情况

这里我们可以取巧,骨架屏组件修改如下:

<div>
   <span v-if="isOpen">我是骨架屏</span>
   <span v-else>
   // 这里可以写成空样式的组件
   <skeleton-loaded></skeleton-loaded>
   <slot></slot>
   </span>
</div>
// skeleton-loaded
<span></span>

当骨架屏结束的时候,出现一个 skeleton-loaded 组件,那么这个组件会走mounted。被我们监听到,最后可以得到骨架屏的加载接触的情况

// 这个就是骨架屏组件加载结束的时间
const skeletonLoadedTime = this.times.find(item =&gt; item.name === 'skeleton-loaded').time

当然,这只是个例子,现实可以随你自己去发挥,确定什么是代表首屏结束的标志,好比我的真实业务情况,就是很简单,找到 element-ui 的 el-table 就可以了

// 这个就是 el-table 组件加载结束的时间
const elTableLoadedTime = this.times.find(item =&gt; item.name === 'el-table').time

结论

通过上面和结合perforemance,我们可以得出下面的时间:

  • 所有组件加载的时间times

  • 首屏的时间(刷新开始到最后一个时间结束):times[times.length - 1](最后一个组件的加载时间) - perforemance.timing.navigationStart(unload上一个文档的时间节点)

  • 框架加载时间的时间:times[0](第一个组件加载的时间) - perforemance.timing.responseEnd(浏览器从服务器收到HTTP请求返回的最后一个字节的时间)

  • 加载js资源所需要的时间:perforemance.timing.responseEnd(浏览器从服务器收到HTTP请求返回的最后一个字节的时间) - perforemance.timing.requestStart(浏览器向服务器发起HTTP请求(包含缓存,本地资源))

其实文中的思路其实特别简单,而且可以根据自己的需求来定制,兼容各种情况,有疑问可以在评论区提出。

谢谢观看,最后祝大家上线没bug,更多关于Vue首屏时间指标采集的资料请关注脚本之家其它相关文章!

来源:https://juejin.cn/post/7186651717960892474

标签:Vue,首屏时间,指标采集
0
投稿

猜你喜欢

  • SQL Server连接查询的实用教程

    2024-01-28 10:41:30
  • Python中关于函数的具体用法范例以及介绍

    2023-06-04 19:48:28
  • vue 实现特定条件下绑定事件

    2023-07-02 16:39:42
  • Python使用selenium实现网页用户名 密码 验证码自动登录功能

    2023-11-14 18:58:45
  • 快速解决Golang Map 并发读写安全的问题

    2024-04-30 10:03:46
  • python使用pandas读写excel文件的方法实例

    2021-04-09 17:44:26
  • python算法题 链表反转详解

    2021-01-26 11:47:35
  • python面试题小结附答案实例代码

    2021-05-04 13:13:08
  • 详解SQL中的DQL查询语言

    2024-01-24 01:51:54
  • CSS sprites图片拼合生成器

    2007-10-15 12:25:00
  • python切片的步进、添加、连接简单操作示例

    2022-09-10 01:26:34
  • 详解Python的条件语句

    2021-03-04 08:27:56
  • python字符串切割:str.split()与re.split()的对比分析

    2022-08-09 20:57:13
  • python 如何在测试中使用 Mock

    2022-01-08 07:41:09
  • 用Python编写一个漏洞验证脚本

    2022-03-23 02:30:43
  • python初学之用户登录的实现过程(实例讲解)

    2023-03-16 17:27:37
  • Python趣味挑战之教你用pygame画进度条

    2022-08-13 15:02:49
  • 关于 Web,你可能不知道的

    2008-09-18 12:09:00
  • 介绍Python中几个常用的类方法

    2023-02-10 09:17:49
  • 常用一些Javascript判断函数

    2024-05-05 09:23:12
  • asp之家 网络编程 m.aspxhome.com