前端性能优化之谈谈通用性能指标及上报策略

8,547 阅读7分钟

背景

性能优化是所有前端人的追求,在这条路上,方法多种多样。这篇文章,说一下可以怎样定义性能指标及上报。

指标

FP

含义

FP,全称 First Paint,翻译为首次绘制,是时间线上的第一个时间点,它代表网页的第一个像素渲染到屏幕上所用时间,也就是页面在屏幕上首次发生视觉变化的时间。

统计逻辑

通过performance.getEntriesByType('paint’),取第一个pain的时间。如:

function getFPTime(){
    const timings = performance.getEntriesByType('paint')[0];
    return timings ? Math.round(timings.startTime) : null
} 

FCP

含义

FCP,全称 First Contentful Paint,翻译为首次内容绘制,顾名思义,它代表浏览器第一次向屏幕绘内容

注意:只有首次绘制文本、图片(包含背景图)、非白色的canvas或SVG时才被算作FCP。

统计逻辑

通过performance.getEntriesByType('paint’),取第二个pain的时间,或者通过Mutation Observer观察到首次节点变动的时间。如:

const domEntries = []
const observer = new MutationObserver((mutationsList)=>{
    for(var mutation of mutationsList) {
        if (mutation.type == 'childList') {
            console.log('A child node has been added or removed.');
        }
        if (mutation.type == 'addedNodes') {
            //TODO新增了节点,做处理,计算此时的可见性/位置/出现时间等信息,然后 push 进数组
            ...
            domEntries.push(mutation) 
        }
    }
});

function getFPTime(){
    const timings = performance.getEntriesByType('paint');
    if(timings.length > 1)return timings[1]
    return timings ? Math.round(timings.startTime) : null
    //伪代码,算 DOM 变化时的最小那个时间,即节点首次变动的时间
    return Math.round(domEntries.length ? Math.min(...domEntries.map(entry => entry.time)) : 0);
} 

MutationObserver 理解及使用补充:

注意

FP与FCP这两个指标之间的主要区别是:FP是当浏览器开始绘制内容到屏幕上的时候,只要在视觉上开始发生变化,无论是什么内容触发的视觉变化,在这一刻,这个时间点,叫做FP。

相比之下,FCP指的是浏览器首次绘制来自DOM的内容。例如:文本,图片,SVG,canvas元素等,这个时间点叫FCP。

FP和FCP可能是相同的时间,也可能是先FP后FCP。

FMP

含义

FMP,全称 First Meaningful Paint,翻译为首次有意义的绘制,是页面主要内容出现在屏幕上的时间, 这是用户感知加载体验的主要指标。目前尚无标准化的定义, 因为很难以通用的方式去确定各种类型页面的关键内容。

统计逻辑

目前没有统一逻辑,阿里有个标准为最高可见增量元素,采用深度优先遍历方法,详细可见:zhuanlan.zhihu.com/p/44933789

其次,可以自定义,比如通 Mutation Observer 观察页面加载的一段时间(如前20s)内页面节点的变化, 即元素新增/移除/隐藏等变化记录下来,这样可以得到页面元素的可见时间点及元素与可视区域的交叉信息等。

然后,自定义一个计算公式,比如根据元素的类型进行权重取值(div 权重1,img 权重2等), 然后取元素与可视区域的交叉区域面积、可见度、 权重值之间的乘积为元素评分。

根据上面得到的信息, 以时间点为X轴, 该时间点可见元素的评分总和为Y轴, 取最高点对应的最小时间为页面主要内容出现在屏幕上的时间。

FID

含义

FID,全称 First Input Delay,翻译为首次输入延迟,是测量用户首次与您的站点交互时的时间(即当他们单击链接/点击按钮/使用自定义的JavaScript驱动控件时)到浏览器实际能够回应这种互动的时间。

统计逻辑

方式一,通过performanceObserver(目前支持性为88.78%)观察类型为first-input的entry,获得其startTime/duration等数即可

方式二,初始化时为特定事件类型(click/touch/keydown)绑定通用统计逻辑事件,开始调用时从event.timeStamp取开始处理的时间(这个时间就是首次输入延迟时间),在事件处理中注册requestIdleCallback事件回调onIdleCallback,当onIdleCallback被执行时,当前时间减开始的event.timeStamp即为duration时间

参考代码:

// 方式一
function getFIDTime(){
    const timings = performance.getEntriesByType('first-input')[0];
    return timings ? timings : null
}
// 方式二,以下代码仅代表思路

['click','touch','keydown'].forEach(eventType => {
    window.addEventListener(eventType, eventHandle);
});

function eventHandle(e) {
    const eventTime = e.timeStamp;
    window.requestIdleCallback(onIdleCallback.bind(this, eventTime, e));
}

function onIdleCallback(eventTime, e) {
    const now = window.performance.now();
    const duration = now - eventTime;

    return {
        duration: Math.round(duration),
        timestamp: Math.round(eventTime)
    }

    ['click','touch','keydown'].forEach(eventType => {
        window.removeEventListener(eventType, eventHandle);
    });
}

Event 对象的更多理解:developer.mozilla.org/zh-CN/docs/…

TTI

含义

TTI,全称 Time To Interactive,翻译为可交互时间,指的是应用在视觉上都已渲染出了,完全可以响应用户的输入了。是衡量应用加载所需时间并能够快速响应用户交互的指标

统计逻辑

与 FMP 相同,很难规范化适用于所有网页的 TTI 指标定义。

统计方式一:谷歌实验室写的npm包,tti-polyfill

import ttiPolyfill from 'tti-polyfill';

ttiPolyfill.getFirstConsistentlyInteractive().then((tti) => {
  ga('send', 'event', {
    eventCategory:'Performance Metrics',
    eventAction:'TTI',
    eventValue: tti,
    nonInteraction: true,
  });
});

统计方式二:在页面加载的一定时间内(如前50s内),以(domContentLoadedEventStart-navigationStart)+5为起始点,循环寻找,找到一个5s的窗口,其中网络请求不超过2个且没有长任务(>50ms),再找到该5秒窗口之前的最后一个长任务,该长任务结束的时间点就是可稳定交互时间。其中长任务可自定义时间或通performance.getEntriesByType('long-task')获取

// 以下代码仅代表思路
const basicTime = 5000;

function getTTITime(startTime,longTaskEntries, resourceEntries,domContentLoadedTime) {

  let busyNetworkInWindow = [];
  let tti = startTime;

  while (startTime + basicTime <= 50000) { //从前50s 中去找
    tti = startTime;
    longTasksInWindow = longTaskEntries.filter(task => {
      return task.startTime < startTime + basicTime && task.startTime + task.duration > startTime;
    });
    if (longTasksInWindow.length) {
      const lastLongTask = longTasksInWindow[longTasksInWindow.length - 1];
      startTime = lastLongTask.startTime + lastLongTask.duration;
      continue;
    }
    busyNetworkInWindow = resourceEntries.filter(request => {
      return !(request.startTime >= startTime + basicTime || request.startTime + request.duration <= startTime);
    });
    if (busyNetworkInWindow.length > 2) {
      const firstRequest = busyNetworkInWindow[0];
      startTime = firstRequest.startTime + firstRequest.duration;
      continue;
    }
    return Math.max(tti, domContentLoadedTime);
  }
  return Math.max(tti, domContentLoadedTime);
}

FCI

含义

FCI,全称 First CPU Idle,翻译为首次CPU空闲时间代表着一个网页已经满足了最小程度的与用户发生交互行为的时刻。当我们打开一个网页,我们并不需要等到一个网页完全加载好了,每一个元素都已经完成了渲染,然后再去与网页进行交互行为。网页满足了我们基本的交互的时间点是衡量网页性能的一个重要指标。

统计逻辑

FCI为在FMP之后,首次在一定窗口时间内没有长任务发生的那一时刻,并且如果这个时间点早于DOMContentLoaded时间,那么FCI的时间为DOMContentLoaded时间,窗口时间的计算函数可以根据Lighthouse提供的计算公式 N = f(t) = 4 * e^(-0.045 * t) + 1 进行自定义设计

FPS

含义

FPS,全称 Frames Per Second,翻译为每秒帧率,表示的是每秒钟画面更新次数,当今大多数设备的屏幕刷新率都是60次/秒。

参考标准:

  • 帧率能够达到 50 ~ 60 FPS 的动画将会相当流畅,让人倍感舒适;
  • 帧率在 30 ~ 50 FPS 之间的动画,因各人敏感程度不同,舒适度因人而异;
  • 帧率在 30 FPS 以下的动画,让人感觉到明显的卡顿和不适感;
  • 帧率波动很大的动画,亦会使人感觉到卡顿

统计逻辑

利用requestAnimationFrame,循环调用,当now大于lastTime+1S时,计算FPS。若小于某个阀值则可以认为当前帧率较差,若连续小于多个阀值,则停止统计,当前页面处于卡顿状态,进入卡顿处理逻辑

更多具体参考:

设备信息

window.navigator.userAgent中可以获取用户设备信息,如图:

Github

window.navigator.connection中可以获取设备网络信息,如:

Github

window.devicePixelRatio可以获取设备像素比。

上报策略

pv/uv

监听各种页面切换的情况;SPA页面,可以监听hashChange

性能数据/设备信息/网络状况

  • 在页面离开前上报,beforeUnload/visibilitychange/pagehide…+sendBeancon/Ajax
  • img 标签+切片+压缩

总结

用图说话,本文内容,整理到了一张图上。

Github
PS:图片有点大,如果出不来,可以到公众号看,链接如下: 前端性能优化之谈谈通用性能指标及上报策略

姊妹篇文章

参考资料

最后

  • 欢迎加我微信(winty230),拉你进技术群,长期交流学习...
  • 欢迎关注「前端Q」,认真学前端,做个有专业的技术人...

GitHub