JS前端性能指标定位FMP使用详解:基于MutationObserver的权重计算法

作者:寒星时间:2026-10-26 09:08:01浏览:448

本文深入解析前端性能指标FMP(First Meaningful Paint)的计算原理,重点介绍基于MutationObserver监听DOM变化并结合元素权重算法的定位方案。通过解析FP、FCP等基础概念,详细阐述如何利用权重计算法识别主角元素,并给出具体代码实现与Chrome环境下的验证结果,误差控制在200ms以内,为前端性能监控提供实用参考。

什么是FMP?

开发者对「白屏时间」这一概念应当不陌生,它曾是早期页面性能评估的核心指标。然而,随着前端工程复杂度的提升,白屏时间已逐渐失去实际参考价值,取而代之的是FMP(首次有效绘制)指标。

在深入探讨之前,先厘清几个相关的基础术语。

  • FP(First Paint,首次绘制):指浏览器渲染出任何与导航前屏幕内容在视觉上存在差异的时间点。
  • FCP(First Contentful Paint,首次内容绘制):标记浏览器渲染出第一个内容DOM节点的时刻,该节点可以是文本、图像、SVG或<canvas>等元素。
  • FMP(First Meaningful Paint,首次有效绘制):标记页面中“主角”元素渲染完成的时间点。所谓主角元素,可能是视频网站的播放器控件、内容网站的页面主体框架,或是资源网站的头图等关键视觉元素。

相较于FP和FCP,FMP是前端开发中更为关注的核心性能指标。Google将其定义为“页面是否具备实际可用性”的时间点。然而,“是否有用”这一标准难以通过通用API量化,因此目前尚无官方标准接口直接输出FMP值。

社区中常采用以下几种方式来实现“相对准确”的FMP计算,这种准确性是相对于具体项目场景而言的。

  • 主动上报:开发者在页面关键“Meaning”位置手动上报时间戳。
  • 权重计算:根据页面元素特征,计算权重最高的元素渲染时间。
  • 趋势计算:在渲染期间,依据DOM变化趋势推算FMP值。

本文将重点阐述第二种方式——权重计算法。

权重定位

所谓权重定位,即按照约定的“权重比”遍历页面元素,计算出“权重值”最大的一个或一组DOM节点,并将其“装载时间点”或“加载结束点”映射为FMP值。

权重计算

节点标记

要对DOM节点进行阶段性标记,必须具备监听DOM变化的能力。幸运的是,HTML5提供了这一能力。

MutationObserver是Mutation Events功能的替代方案,属于DOM3 Events规范的一部分。它允许在指定的DOM发生变化时执行回调函数。

MutationObserver包含三个核心方法:

  • disconnect():阻止MutationObserver实例继续接收通知,直到再次调用observe()方法。在此期间,观察者对象包含的回调函数均不再被调用。
  • observe():配置MutationObserver,当DOM更改匹配给定选项时,通过回调函数开始接收通知。
  • takeRecords():从通知队列中移除所有待处理的通知,并将其返回到一个新的MutationRecord对象数组中。
global.mo = new MutationObserver(() => { 
    /* callback: DOM 节点设置阶段性标记 */
});
/**
 * mutationObserver.observe(target[, options])
 * target - 需要观察变化的 DOM Node。
 * options - MutationObserverInit 对象,配置需要观察的变化项。
 * 更多 options 的介绍请参考 https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserverInit#%E5%B1%9E%E6%80%A7
 **/
global.mo.observe(document, {
  childList: true,  // 监听子节点变化(如果subtree为true,则包含子孙节点)
  subtree: true // 整个子树的所有节点
});

下图粗略解析了正常单页面的渲染过程。

什么是FMP?

  • 预备阶段:导航阶段,处于连接相应资源的过程中。
  • 阶段一:首字节渲染阶段,即FCP,表现为DOM树的第一次有效变化。
  • 阶段二:基本框架渲染完成。
  • 阶段三:获取到数据,并渲染到视图上。
  • 阶段四:图片加载完成,此阶段的加载过程通常不被标记为FMP。

实际上,在第一、第三阶段之间存在着大量的DOM变化。由于Mutation Observer事件的触发是异步的,即等待当前“阶段”所有DOM操作结束后才触发,因此不会同步响应每一次DOM变动。

Mutation Observer具有以下特点:

  • 它等待所有脚本任务完成后才会运行(即采用异步触发方式)。
  • 它将DOM变动记录封装成一个数组进行处理,而不是一条条个别处理DOM变动。
  • 它既可以观察DOM的所有类型变动,也可以指定只观察某一类变动。

在load事件触发后,各个阶段的tag已经被打到标签上了。

什么是FMP?

此处以『_ti』作为标记key。

什么是FMP?

在打标记的同时,需要记录下当前的时间节点,以备后续使用。

// 伪代码
function callback() {
    global.timeStack[++_ti] = performance.now(); // 记时间
    doTag(_ti); // 打标记
}

标记打完后,等待load事件发生时进行计算反推。

计算权重值

一般来说,

  • 视图占比越大的元素越有可能是主角元素。
  • 视频比图片更可能是主角元素。
  • svg和canvas也很重要。
  • 其他元素都可以按普通DOM计算。
  • 背景图片视情况而定,可记可不记。

第一步:简单粗暴,按大小计算。

// 伪代码
function weightCompute(node){
    let {
        width,
        height,
        left,
        top
    } = node.getBoundingClientRect();
    // 排除视图外的元素
    if(isOutside(width, height, left, top)){
        return 0;
    }
    let wts = TAG_WEIGHT_MAP[node.tagName]; // 约定好的权重比
    let weight = width * height * wts; // 直接乘,或者更细粒度的计算 wts(width, height, wts)
    return {
        weight, 
        wts, 
        tagName: node.tagName, 
        ti: node.getAttribute("_ti"),
        node
    };
}

第二步:根据权重值推导主角元素。

在我们的约定权重算法下,权重最大的元素即为我们推导出的主角元素。

// 伪代码
function getCoreNode(node){
    let list = nodeTraversal(node); // 递归计算每个标记节点的权重值
    return getNodeWithMaxWeight(list); // weight 最大的元素
}

第三步:根据元素类型取时间。

不同的元素获取时间的方式并不相同。

  • 普通元素:按标记点时间计算。
  • 图片和视频:按资源响应结束时间计算。
  • 带背景元素:可以以背景资源响应结束时间计算,也可以按普通元素计算。
// 伪代码
function getFMP(){
    let coreObj = getCoreNode(document.body),
        fmp = -1;
    let {
        tagName,
        ti,
        node
    } = coreObj;
    switch(tagName){
        case 'IMG':
        case 'VIDEO':
            let source = node.src;
            let { responseEnd } = performance.getEntries().find(item => item.name === source);
            fmp = responseEnd || -1;
            break;
        default:
            if(node.style.backgroundImage){
                // 普通元素的背景处理
            }else{
               fmp = global.timeStack[+ti]; 
            }
    }
    return fmp;
}

回归验证

以我们的demo页为例,这是一个类似的电商网站,我们希望拿到「阶段二」或「阶段三」的时间点作为我们的FMP值。

什么是FMP?

因为我们并不希望「主角元素」的背景或者「图片主角元素」的响应时间算在FMP的值内,所以,我们将「图片」「视频」等资源元素降级成普通元素计算。

在Chrome [ Disable cache / Fast 3G ]条件下我们进行模拟验证。

什么是FMP?

什么是FMP?

计算得到的FMP值为4730.7ms,Chrome Performance监控的值在4950ms左右,误差在200ms左右。

如果将限速放开,FMP的取值将更接近我们希望的「First Meaning Paint」。

以上就是JS前端性能指标定位FMP使用详解的详细内容,更多关于JS前端性能指标定位FMP的资料请关注本站其它相关文章!

作者最新文章