找回密码
 会员注册
查看: 44|回复: 0

统计页面首屏时间,很多人第一步就错了

[复制链接]

3

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2024-9-20 03:29:55 | 显示全部楼层 |阅读模式
前言前端页面性能对用户留存、用户直观体验有着重要作用。这样的话如何更好的监控前端页面性能就变的十分重要。前端页面的性能监控主要分为两个方式:一种叫做 合成监控SyntheticMonitoring,SYN。就是在一个模拟场景里,提交一个需要做性能审计的页面,通过一系列的工具、规则去运行页面,提取一些性能指标,得出一个审计报告。合成监控中最近比较流行的是Google的Lighthouse另一种是 真实用户监控RealUserMonitoring,RUM。监控真实的用户访问数据,上报数据到服务器,然后经过数据清洗加工,得到最终的性能数据。在前端性能监控中有一个非常重要的指标就是首屏时间,因为首屏时间直接反应了用户多久能看到页面的主要内容,这决定了用户体验。这样的话,如何取到准确的首屏时间对我们来说就变的非常重要。本文就结合之前的实践,聊一聊首屏时间如何计算。Performance在SSR(服务端渲染)的应用中,我们认为html的body渲染完成的时间就是首屏时间。我们通常使用W3C标准的Performance对象来计算首屏时间。Performance经常被用于采集性能数据,因为对象内置了几乎所有常用前端需要的性能参数。imagePerformance包含了四个属性:memory、navigation、timeOrigin、timing,以及一个事件处理程序onresourcetimingbufferfull。下面我们简单介绍一下Performance的api。memorymemory这个属性提供了一个可以获取到基本内存使用情况的对象MemoryInfoperformance.memory = {  jsHeapSizeLimit, // 内存大小限制,单位是字节B  totalJSHeapSize, // 可使用的内存大小,单位是字节B  usedJSHeapSize   // JS对象占用的内存大小,单位是字节B}navigation返回PerformanceNavigation对象,提供了在指定的时间段发生的操作相关信息,包括页面是加载还是刷新、发生了多少重定向等。performance.navigation = {  redirectCount: '',  type: ''}timeOrigin返回性能测量开始的时间的高精度时间戳timing返回PerformanceTiming对象,包含了各种与浏览器性能相关的数据,提供了浏览器处理页面的各个阶段的耗时。下面是常用时间点计算window.onload = function() {  var timing  = performance.timing;  console.log('准备新页面时间耗时: ' + timing.fetchStart - timing.navigationStart);  console.log('redirect 重定向耗时: ' + timing.redirectEnd  - timing.redirectStart);  console.log('Appcache 耗时: ' + timing.domainLookupStart  - timing.fetchStart);  console.log('unload 前文档耗时: ' + timing.unloadEventEnd - timing.unloadEventStart);  console.log('DNS 查询耗时: ' + timing.domainLookupEnd - timing.domainLookupStart);  console.log('TCP连接耗时: ' + timing.connectEnd - timing.connectStart);  console.log('request请求耗时: ' + timing.responseEnd - timing.requestStart);  console.log('白屏时间: ' + timing.responseStart - timing.navigationStart);  console.log('请求完毕至DOM加载: ' + timing.domInteractive - timing.responseEnd);  console.log('解释dom树耗时: ' + timing.domComplete - timing.domInteractive);  console.log('从开始至load总耗时: ' + timing.loadEventEnd - timing.navigationStart);}通过上面的介绍,我们可以轻松的得到首屏时间domLoadedTime = timing.domContentLoadedEventStart - timing.navigationStartFMP但是随着Vue和React等前端框盛行,导致Performance无法准确的监控到页面的首屏时间。因为页面的body是空,浏览器需要先加载js,然后再通过js来渲染页面内容。那我们使用什么数据来当做首屏时间呢?在Lighthouse中我们可以得到FMP值,FMP(全称FirstMeaningfulPaint,翻译为首次有效绘制)表示页面的主要内容开始出现在屏幕上的时间点,它是我们测量用户加载体验的主要指标。我们可以认为FMP的值就是首屏时间,但是浏览器并没有把FMP的数据提供出来。那我们如何计算呢?整个计算流程分为两个下面两个部分:1、监听元素加载,主要是为了计算Dom的分数2、计算分数的曲率,计算出最终的FMP值初始化监听initObserver() {  try {    if (this.supportTiming()) {      this.observer = new MutationObserver(() => {        let time = Date.now() - performance.timing.fetchStart;        let bodyTarget = document.body;        if (bodyTarget) {          let score = 0;          score += calculateScore(bodyTarget, 1, false);          SCORE_ITEMS.push({            score,            t: time          });        } else {          SCORE_ITEMS.push({            score: 0,            t: time          });        }      });    }    this.observer.observe(document, {      childList: true,      subtree: true    });    if (document.readyState === "complete") {      this.mark = 'readyState';      this.calFinallScore();    } else {      window.addEventListener(        "load",        () => {          this.mark = 'load';          this.calFinallScore();        },        true      );      window.addEventListener(        'beforeunload',        () => {          this.mark = 'beforeunload';          this.calFinallScore();        },        true      )      const that = this;      function listenTouchstart() {        if(Date.now() > 2000) {          that.calFinallScore();          this.mark = 'touch';          window.removeEventListener('touchstart', listenTouchstart, true);        }      }      window.addEventListener(        'touchstart',        listenTouchstart,        true      )    }  } catch (error) {}}我们通过MutationObserver来监听Dom的变化,然后计算当前时刻Dom的分数。有人可能会问,如果Dom每一次变化,都进行监听,是不是会特别消耗页面的性能?其实MutationObserver在执行回调时是批量执行,有些类似Vue等前端框架的渲染过程。计算分数function calculateScore(el, tiers, parentScore) {  try {    let score = 0;    const tagName = el.tagName;    if ("SCRIPT" !== tagName & "STYLE" !== tagName & "META" !== tagName & "HEAD" !== tagName) {      const childrenLen = el.children ? el.children.length : 0;      if (childrenLen > 0) for (let childs = el.children, len = childrenLen - 1; len >= 0; len--) {        score += calculateScore(childs[len], tiers + 1, score > 0);      }      if (score = fmps[o - 1].t) {    let l = fmps[o].score - fmps[o - 1].score;    (!record || record.rate 
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-26 12:57 , Processed in 0.542816 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表