

统计页面首屏时间,很多人第一步就错了
source link: https://mp.weixin.qq.com/s?__biz=MzU0OTExNzYwNg%3D%3D&%3Bmid=2247489140&%3Bidx=1&%3Bsn=28ae4578909cdf69e6a4476de5cde01b
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

前言
前端页面性能对用户留存、用户直观体验有着重要作用。这样的话如何更好的监控前端页面性能就变的十分重要。前端页面的性能监控主要分为两个方式:
一种叫做 合成监控
Synthetic Monitoring, SYN
。就是在一个模拟场景里,提交一个需要做性能审计的页面,通过一系列的工具、规则去运行页面,提取一些性能指标,得出一个审计报告。合成监控中最近比较流行的是 Google
的 Lighthouse
另一种是 真实用户监控
Real User Monitoring,RUM
。监控真实的用户访问数据,上报数据到服务器,然后经过数据清洗加工,得到最终的性能数据。
在前端性能监控中有一个非常重要的指标就是首屏时间,因为首屏时间直接反应了用户多久能看到页面的主要内容,这决定了用户体验。这样的话,如何取到准确的首屏时间对我们来说就变的非常重要。本文就结合之前的实践,聊一聊首屏时间如何计算。
Performance
在 SSR(服务端渲染)的应用中,我们认为 html
的 body
渲染完成的时间就是首屏时间。我们通常使用 W3C 标准的 Performance
对象来计算首屏时间。
Performance
经常被用于采集性能数据,因为对象内置了几乎所有常用前端需要的性能参数。

Performance
包含了四个属性: memory
、 navigation
、 timeOrigin
、 timing
,以及一个事件处理程序 onresourcetimingbufferfull
。下面我们简单介绍一下 Performance
的 api
。
memory
memory
这个属性提供了一个可以获取到基本内存使用情况的对象 MemoryInfo
performance.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.navigationStart
FMP
但是随着 Vue
和 React
等前端框盛行, 导致 Performance
无法准确的监控到页面的首屏时间。因为页面的 body
是空,浏览器需要先加载 js
, 然后再通过 js
来渲染页面内容。那我们使用什么数据来当做首屏时间呢?
在 Lighthouse
中我们可以得到 FMP 值,FMP(全称 First Meaningful Paint,翻译为首次有效绘制)表示页面的主要内容开始出现在屏幕上的时间点,它是我们测量用户加载体验的主要指标。我们可以认为 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 <= 0 && !parentScore) {
if (!(el.getBoundingClientRect && el.getBoundingClientRect().top < WH)) return 0;
}
score += 1 + .5 * tiers;
}
return score;
} catch (error) {
}
}
通过上面的代码,我们可以得到计算分数的步骤
1、从 body
元素开发递归计算
2、会排查无用的元素标签比较 SCRIPT
等
3、如果元素超出屏幕就认为是 0 分
4、第一层的元素是 1 分,第二次的元素是 1 + (层数 * 0.5),也就是 1.5 分,依次类推,最终得打整个 Dom
数的总体分数
计算出 FMP
我们通过 MutationObserver
得到了一个数组,数组的每一项就是每次 Dom
变化的时间和分数。那么我们怎么计算出想要的 FMP
的值呢?
let fmps = getFmp(SCORE_ITEMS);
let record = null
for (let o = 1; o < fmps.length; o++) {
if (fmps[o].t >= fmps[o - 1].t) {
let l = fmps[o].score - fmps[o - 1].score;
(!record || record.rate <= l) && (record = {
t: fmps[o].t,
rate: l
});
}
}
通过上面的代码,我们会得到最终的 FMP
的值,就是变化最大的这个 DOM
变化。

总结
到这里我们就基本把首屏时间的计算方式介绍完毕。总结为一句话,就是 SSR
使用 Dom
渲染结束的时间, SPA
的项目使用 FMP
的时间。
本月文章预告
预告下,接下来我们会陆续发布转转在多端 SDK、移动端等基础架构和中台技术相关的实践与思考,欢迎大家关注公众号 “大转转 FE”,期望与大家多多交流
Recommend
-
290
Vue SPA 首屏加载优化实践 2017年12月08日 02:32 · 阅读 27087
-
95
-
68
背景随着公司业务的不断壮大,最近老是有用户反应公司APP内的商城打开比较慢,这可不行啊,慢了容易流失用户,流失用户减少公司业绩,公司业绩少我的年终奖就少…………,所以为了公司,也为了自己,开始优化之路。商城系统是去年开发的,是一个基于vue2.0的spa项目,
-
60
作者:江敏熙 贝聊前端开发工程师 前言 我司的官网首页——贝聊官网,首屏有一个自动播放的背景视频,一直被诟病视频加载慢、播放卡。刚开始以为是文件太大,或者是网速太慢,但当我去优化它的时候,发现并没有预想的简单。本文记录了优化过程和经验总结,希望能对读...
-
78
-
58
-
7
首屏时间,你说你优化了,那你倒是计算出给给我看啊!大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的初心
-
5
数据库选型规划上,很多人第一步就做错了…… 作者:白鳝 2022-09-21 09:09:49 数据库是上接企业应用,下连IT基础设施的关键性IT组件,其选型的影响上到应用开发,下到云平台,所以不得不慎重。
-
4
数据库选型规划上,很多人第一步就做错了…… 白鳝 2022-09-21 09:47:36 这个周末一直在帮一个客户构思一个数据库应...
-
5
我们知道,用户体验是 Web 产品最为重要的部分。尽可能减少首屏加载时间,更...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK