前言
前端页面性能对用户留存、用户直观体验有着重要作用。这样的话如何更好的监控前端页面性能就变的十分重要。前端页面的性能监控主要分为两个方式:
一种叫做合成监控 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”,期望与大家多多交流
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!