RAIL性能评估模型从以下四个方面提出了要求:

针对该评估模型,Google 提出了以用户为中心的四个衡量指标:

分别对应渲染过程中的若干阶段,截图如下:

那么如何具体统计这些指标呢?

旧方法的问题

过去的某些统计方法是会损耗性能的,例如使用 rAF 检测过长的帧。但是缺点很明显,轮询会影响性能。

(function detectLongFrame() {
    var lastFrameTime = Date.now();
    requestAnimationFrame(function() {
        var currentFrameTime = Date.now();

        if (currentFrameTime - lastFrameTime > 50) {
            // Report long frame here...
        }

        detectLongFrame(currentFrameTime);
    });
}());

下面来看看具体针对这四个指标的检测方法以及优化方式。

FP/FCP

跟踪 FP/FCP,监听 paint 事件,不得不说这样的确太方便了。

const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
        // `name` will be either 'first-paint' or 'first-contentful-paint'.
        const metricName = entry.name;
        const time = Math.round(entry.startTime + entry.duration);

        ga('send', 'event', {
            eventCategory: 'Performance Metrics',
            eventAction: metricName,
            eventValue: time,
            nonInteraction: true,
        });
    }
});
observer.observe({entryTypes: ['paint']});

针对 FP,也就是优化首屏方案大致包括以下几种,当然实现难度各异:

FMP

关于页面有效内容,或者“Hero element”,由于依赖具体实现,并没有给出通用方法。 具体可以使用performance api度量指标。

TTI

这个指标我第一次听说,首次可交互时间。不过其实在前端渲染完成之前,例如展示 skeleton 页面骨架时,对于用户而言就是无法交互的状态,只能看不能点。

文章中指出在添加到 PerformanceObserver 之前,可以使用polyfill完成:

import ttiPolyfill from './path/to/tti-polyfill.js';

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

long task

浏览器在响应用户交互事件时,向队列中添加任务,等待主线程依次执行。 由于主线程还要负责执行 JS,当处理时间过长时,就会导致任务无法及时得到执行,给用户的感觉就是未响应。 通常定义超过 50ms 响应时间的任务就是 long task 了。

和 FP 一样,可以直接使用 PerformanceObserver:

const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
        ga('send', 'event', {
            eventCategory: 'Performance Metrics',
            eventAction: 'longtask',
            eventValue: Math.round(entry.startTime + entry.duration),
            eventLabel: JSON.stringify(entry.attribution),
        });
    }
});
observer.observe({entryTypes: ['longtask']});

关于优化方式,可以使用requestIdleCallback,不重要的任务例如发送日志等操作可以放在里面执行。但是支持度不高

input latency

滚动和动画的延迟是难以统计的,但是针对点击事件的响应延迟,可以采用如下方法统计:事件触发的时间到最终响应时的时间差就是延迟了,当超过 100 毫秒时进行记录:

const subscribeBtn = document.querySelector('#subscribe');
subscribeBtn.addEventListener('click', (event) => {
    const lag = performance.now() - event.timeStamp;
    if (lag > 100) {
        ga('send', 'event', {
            eventCategory: 'Performance Metric'
            eventAction: 'input-latency',
            eventLabel: '#subscribe:click',
            eventValue: Math.round(lag),
            nonInteraction: true,
        });
    }
});

参考资料

Slide