IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    狠狠地研究了下 PerformanceObserver API

    张 鑫旭发表于 2023-08-27 08:34:24
    love 0

    by zhangxinxu from https://www.zhangxinxu.com/wordpress/?p=10950 鑫空间-鑫生活
    本文欢迎分享与聚合,全文转载就不必了,尊重版权,圈子就这么大,若急用可以联系授权。

    乌鸦南瓜封面图

    一、Observer家族

    没想到Observer家族还有个PerformanceObserver,这就有点匪夷所思,就是,之前我明明好好梳理过浏览器目前支持的Observer,但是都2023年了,突然出现个PerformanceObserver,就非常让人觉得不真实,之前怎么就没看到呢?

    匪夷所思

    MutationObserver可以用来观察DOM元素的变化,ResizeObserver可以用来观察DOM元素尺寸的变化,IntersectionObserver则可以观察元素和浏览器视窗相交的情况。

    而本文要介绍的 PerformanceObserver 则可以对页面的各项性能指标进行观察。

    二、关于性能 Performance API

    Performance API是个庞大的家族,根本学不动的,看看下面这些(参考自MDN文档),知识密度也不是一般的高。

    PerformanceEntryPerformanceEntryPerformanceMarkPerformanceMarkPerformanceMeasurePerformanceMeasurePerformancePaintTimingPerformancePaintTimingPerformanceResourceTimingPerformanceResourceTimingPerformanceNavigationTimingPerformanceNavigationTimingPerformanceElementTimingPerformanceElementTimingPerformanceLongTaskTimingPerformanceLongTaskTimingPerformanceEventTimingPerformanceEventTimingTaskAttributionTimingTaskAttributionTimingLargestContentfulPaintLargestContentfulPaintPerformancePerformancePerformanceObserverPerformanceObserverLayoutShiftLayoutShiftVisibilityStateEntryVisibilityStateEntry

    PerformanceObserver是其中非常重要的一部分内容。

    三、包含的性能指标

    PerformanceObserver中包含如下表所示的一系列性能指标(可以通过 PerformanceObserver.supportedEntryTypes 返回,不同浏览器的支持情况可能会有差异):

    指标 释义 Chrome logo Firefox logo Safari logo
    back-forward-cache-restoration – ✔ ✘ ✘
    element 元素加载时间,实例项是 PerformanceElementTiming 对象。 ✔ ✘ ✘
    event 事件延迟,实例项是 PerformanceEventTiming 对象。 ✔ ✔ ✘
    first-input 用户第一次与网站交互(即点击链接、点击按钮或使用自定义的JavaScript控件时)到浏览器实际能够响应该交互的时间,称之为First input delay – FID。 ✔ ✔ ✘
    largest-contentful-paint 屏幕上触发的最大绘制元素,实例项是 LargestContentfulPaint 对象。 ✔ ✘ ✘
    layout-shift 元素移动时候的布局稳定性,实例项是 LayoutShift对象。 ✔ ✘ ✘
    long-animation-frame 长动画关键帧。 ✔ ✘ ✘
    longtask 长任务实例,归属于 PerformanceLongTaskTiming 对象。 ✔ ✘ ✘
    mark 用户自定义的性能标记。实例项是 PerformanceMark 对象。 ✔ ✔ ✔
    measure 用户自定义的性能测量。实例项是 PerformanceMeasure 对象。 ✔ ✔ ✔
    navigation 页面导航出去的时间,实例项是 PerformancePaintTiming 对象。 ✔ ✔ ✔
    paint 页面加载时内容渲染的关键时刻(第一次绘制,第一次有内容的绘制,实例项是 PerformancePaintTiming 对象。 ✔ ✔ ✔
    resource 页面中资源的时间信息,实例项是 PerformanceResourceTiming 对象。 ✔ ✔ ✔
    taskattribution 长期任务有重大贡献的工作类型,实例项是 TaskAttributionTiming 对象。 ✘ ✘ ✘
    soft-navigation – ✔ ✘ ✘
    visibility-state 页面可见性状态更改的时间,即选项卡何时从前台更改为后台,反之亦然。实例项是 VisibilityStateEntry 对象。 ✔ ✘ ✘

    PS:测试浏览器,Chrome 114,Firefox 116 和 Safari 16.5.2.

    可以看到,基本上,每一种性能指标都有一个对应的专属对象类型。

    其中,有些指标属于核心指标。

    Web性能核心指标

    • LCP,Largest Contentful Paint,对应largest-contentful-paint类型,是加载性能指标。
    • FID,First Input Delay,对应first-input类型,是交互性能指标。
    • CLS,Cumulative Layout Shift,对应layout-shift类型,是视觉稳定性指标。

    可以重点关注下,至于其他一些时间类型,及其对应的对象,咳咳……内容实在太多了,学不完,实在是学不完,所以,略过,等哪天真正需要了,可以再好好看一看。

    四、paint类型与首次渲染时间

    下面看下PerformanceObserver中 ‘paint’ 的运行结果。

    看下面的代码和实例,在页面头部写入:

    const observer = new PerformanceObserver(entryList => {
        for (const entry of entryList.getEntries()) {
            console.dir(entry);
        }
    });
    observer.observe({
      entryTypes: ['paint']
    });

    在Chrome浏览器下,会输出两个条目对象,如下截图所示:

    Chrome下 paint 条目

    分别是开始渲染和开始有内容(文字或图片所在DOM)渲染。

    例如,有内容渲染的数据结构:

    {
      "name":"first-contentful-paint",
      "entryType":"paint",
      "startTime":220.80000001192093,
      "duration":0
    }

    Firefox下则只有一个条目,因为支持的类型更少。

    Firefox下的支持情况

    其中可参考的指标就是 startTime,表示什么时候内容开始渲染,基本上,可以近似表示首屏内容呈现的时间。

    根据我的测试,第一次进入的时候,时间都会比较长,200ms+,无论哪个浏览器都是,然后再刷新就很快了,都是100ms以内,我估计,是因为类似CSS文件这些资源被缓存了(外链CSS可以阻塞渲染),所以更快了。

    眼见为实,您可以狠狠地点击这里:PerformanceObserver首屏渲染时间demo

    //zxx: Chrome浏览器下paint有多个类型,我们将entryList.getEntries()替换成(例如)entryList.getEntriesByName('first-contentful-paint')。

    五、看看largest-contentful-paint的运行结果

    虽然LCP是重要的性能指标,但是目前仅Chrome浏览器支持。

    运行代码和’paint’类似,这里就不展示,感兴趣的可以猛击这里访问:largest-contentful-paint运行测试demo

    输出的结果很奇怪。

    在我本地(右上角广告图不加载的时候),最大内容渲染元素是<h1>标题,线上则是右上角的广告图。

    以及,再输出的是pre元素,并且是在定时器触发内容更新后显示。

    按照我的理解,最大渲染元素不应该是下面的色彩斑斓的底纹大色框吗?

    后来我再想,大应该指的是内容,而不是渲染面积。

    于是我又塞入了很多文字内容,结果还是一样。

    最大内容元素

    这个元素的选取规则实在令人费解。

    于是我又仔细查看了下MDN文档,大致知道原因了。

    The LargestContentfulPaint interface provides timing information about the largest image or text paint before user input on a web page.

    需要是在页面可交互之前渲染的元素,并且要在视区范围之内(元素所有区域)。

    基于这两点,我又重新修改了下页面内容,包括高度减小,内容增多,然后一刷新,哦吼,成了!

    最大渲染内容

    开心,又弄明白一个小知识。

    六、过去不太方便获取的first-input指标

    first-input指的是用户页面首次交互时间,我一开始还觉得是可交互时间,幸好测试了下。

    const observer = new PerformanceObserver(entryList => {
        for (const entry of entryList.getEntries()) {
            console.dir(entry);
        }
    });
    observer.observe({
      entryTypes: ['first-input']
    });

    例如,上面的测试代码,在页面刷新后,是没有任何输出的。

    只有当你点击页面,console语句才会执行。

    这个API倒是可以用来统计页面是否被用户交互过,以及停留时间,也不知道可不可以用来识别是否是机器行为。

    以后有机会试一下。

    其输出结果示意:

    起止时间示意

    {
        "name": "pointerdown",
        "entryType": "first-input",
        "startTime": 2953.4000000953674,
        "duration": 0,
        "navigationId": 1,
        "processingStart": 2953.7000002861023,
        "processingEnd": 2953.7000002861023,
        "cancelable": true
    }

    其中startTime就是从页面开始可交互,到第一次交互的时间间隔,name是事件类型,点击是pointerdown,键盘访问是keydown。

    我测试了下,在PC端,似乎只有点击和键盘方位才能触发,鼠标hover控件元素,以及鼠标滚轮滚动都是不会触发的。

    眼见为实,您可以狠狠地点击这里:first-input首次交互时间测试demo

    七、接下来是mark和measure

    Web中的Performance中性能都是围绕时间线timeline展开的,。

    从Chrome,Firefox等浏览器的性能分析工具可以看出:

    时间线

    而需要知道某一段时间线的性能,就需要知道这个时间线的起止点。

    这个起止点的标记就叫做mark,期间的性能测量就是measure。

    这两个动作对应两个专门的对象接口,前者是PerformanceMark,可以给任意位置命名和添加标记,后者是PerformanceMeasure,是两个标记之间的时间度量。

    我们可以借助这两个对象精确测量某些性能指标。

    一种方法是使用Performance API,还有就是使用PerformanceObserver。

    这里通过一个例子演示下各自的使用。

    假设页面上有两个元素,一个id是output,另外一个是footer,则测试代码如下(使用定时器模拟时间间隔):

    // mark标记
    performance.mark('test-start', {
        startTime: 0,
          detail: { htmlElement: 'output' }
    });
    
    setTimeout(() => {
        performance.mark('test-end', {
            startTime: 360,
            detail: { htmlElement: 'footer' }
        });    
        
        const testMeasure = performance.measure(
          "test-start",
          "test-end"
        );
        
        console.dir(testMeasure);
    }, 360);
    
    const observer = new PerformanceObserver(entryList => {
        entryList.getEntries().forEach((entry) => {
            var logMark = '';
            var logMeasure = '';
            if (entry.entryType === 'mark') {
                  logMark = `${entry.name}的startTime是: ${entry.startTime}`;
                  console.log(logMark);
            }
            if (entry.entryType === 'measure') {
                  console.log(logMeasure = `${entry.name}的duration时间是: ${entry.duration}`);
            }
        });
    });
    observer.observe({
          entryTypes: ['mark', 'measure']
    });

    此时,就可以看到执行开始时间,和时间间隔等信息了。

    渲染时间查看

    此时的 duration 应该就是两个DOM元素之间的渲染时间了吧。

    眼见为实,您可以狠狠地点击这里:mark和measure类型测试demo

    八、好了,打住,该结束了

    经过这一波阅读,测试和撰写,我已经大致搞清楚PerformanceObserver的花花草草了……嗯嗯嗯……是这样的,等哪一天遇到页面卡顿,可以用这个排查看看。

    至于性能统计与上报也是个不常见的需求。

    所以,了解下就好了。

    平常那些项目,只要正常开发,以目前的浏览器性能,都会很流畅的。

    另外,还是要亲自测试,只靠看文档,很多东西是看不出来的,并且文档很多内容都是缺失的。

    OK,马上9月了,是时候拧紧发条了。

    好了,内容已经够多的了,就不再多扯淡了。

    感谢您的阅读,行为仓促,如有错误,欢迎指正。

    比心~

    💓 💗 💖 💘 💝

    本文为原创文章,欢迎分享,勿全文转载,如果实在喜欢,可收藏,永不过期,且会及时更新知识点及修正错误,阅读体验也更好。
    本文地址:https://www.zhangxinxu.com/wordpress/?p=10950

    (本篇完)



沪ICP备19023445号-2号
友情链接