首屏加载时间计算
文章:https://cloud.tencent.com/developer/article/1850013
function getFirstScreenTime() {
const details = [];
return new Promise(function (resolve, reject) {
// 5s之内先收集所有的dom变化,并以key(时间戳)、value(dom list)的结构存起来。
const observeDom = observeDoc(details);
setTimeout(function () {
observeDom.disconnect();
console.log('details', details);
resolve(details);
}, 5000);
}).then(function (details) {
return compareTime(details);
});
}
// 监听文档
function observeDoc(details) {
var observeDom = new MutationObserver(function (mutations) {
if (!mutations || !mutations.forEach) return;
var detail = {
time: performance.now(),
roots: [],
imgs: [],
};
mutations.forEach(function (mutation) {
if (!mutation || !mutation.addedNodes || !mutation.addedNodes.forEach) {
return;
}
mutation.addedNodes.forEach(function (ele) {
const nodeName = ele.nodeName.toLocaleLowerCase();
const isImg = nodeName === 'img';
const isValidEle = isEleValid(ele, detail.roots);
if (isValidEle) {
detail.roots.push(ele);
if (isImg) {
detail.imgs.push(ele);
}
}
});
});
if (detail.roots.length) {
details.push(detail);
}
});
observeDom.observe(document, {
childList: true,
subtree: true,
});
return observeDom;
}
// 比较首屏内DOM时间
function compareTime(details) {
// 分析上面收集到的数据,返回最终的结果
const domRenderTime = [];
const allImgs = [];
const allImgLoadTimes = [];
details.forEach(function (detail) {
// 拿到所有首屏DOM 的 渲染时间
for (var i = 0; i < detail.roots.length; i++) {
const ele = detail.roots[i];
if (isInFSAndVisible(ele)) {
domRenderTime.push(detail.time);
// 同一个 detail 的 节点,时间一样,不用继续收集了
break;
}
}
// 拿到所有首屏中的图片
for (var i = 0; i < detail.imgs.length; i++) {
const ele = detail.imgs[i];
if (isInFSAndVisible(ele)) {
allImgs.push(ele.src);
}
}
});
// 遍历当前请求的图片中,如果有开始请求时间在首屏dom渲染期间的,则表明该图片是首屏渲染中的一部分,
// 所以dom渲染时间和图片返回时间中大的为首屏渲染时间
window.performance.getEntriesByType('resource').forEach(function (resource) {
const url = resource.name;
if (resource.initiatorType === 'img' && allImgs.indexOf(url) > -1) {
allImgLoadTimes.push(resource.responseEnd);
}
});
console.log('allImgs', allImgs);
console.log('domRenderTime', domRenderTime);
console.log('allImgLoadTimes', allImgLoadTimes);
const resultTime = Math.max(...domRenderTime, ...allImgLoadTimes);
return resultTime;
}
// 节点是否有效
function isEleValid(ele, arr) {
const ignoreEleList = ['script', 'style', 'link', 'br'];
const nodeName = ele.nodeName.toLocaleLowerCase();
const isEleNode = ele.nodeType === 1;
const isRenderNode = ignoreEleList.indexOf(nodeName) !== -1;
if (isEleNode && isRenderNode) {
if (isInFS(ele)) {
if (!isElderOrExitEle(ele, arr)) {
return true;
}
}
}
return false;
}
// dom 是否有效,过滤
function isEleValid(ele, arr) {
const ignoreEleList = ['script', 'style', 'link', 'br'];
const nodeName = ele.nodeName.toLocaleLowerCase();
const isEleNode = ele.nodeType === 1;
const isRenderNode = ignoreEleList.indexOf(nodeName) === -1;
if (isEleNode && isRenderNode) {
if (isInFS(ele)) {
if (!isElderOrExitEle(ele, arr)) {
return true;
}
// 图片另算
else if (nodeName === 'img') {
return true;
}
}
}
return false;
}
// 是否已经存在节点,或者是长辈节点
function isElderOrExitEle(target, arr) {
if (!target || target === document.documentElement) {
return false;
// 说明数组已经存在这个节点
} else if (arr.indexOf(target) !== -1) {
return true;
// 不要长辈节点
} else {
return isElderOrExitEle(target.parentElement, arr);
}
}
// 位置是否首屏中
function isInFS(target) {
if (!target || !target.getBoundingClientRect) return false;
var rect = target.getBoundingClientRect(),
screenHeight = window.innerHeight,
screenWidth = window.innerWidth;
return (
rect.left >= 0 &&
rect.left < screenWidth &&
rect.top >= 0 &&
rect.top < screenHeight
);
}
// 首屏中,并且可见
function isInFSAndVisible(target) {
if (!target || !target.getBoundingClientRect) return false;
var rect = target.getBoundingClientRect();
const isvisible = isVisible(target);
const hasArea = rect.width > 0 && rect.height > 0;
return isInFS(target) && hasArea && isvisible;
}
// 是否可见
function isVisible(target) {
target.style.opacity = 'inherit';
var rawOpacity = getStyle(target, 'opacity');
var visibility = getStyle(target, 'visibility');
var opacity = +rawOpacity;
if (opacity != rawOpacity) {
opacity = 1; // can not check old browser
}
if (opacity > 0.09 || visibility === 'visible') {
return true;
}
return false;
}
// 获取样式
function getStyle(el, name) {
if (window.getComputedStyle) {
return getComputedStyle(el, null)[name];
}
if (el.currentStyle) {
return el.currentStyle[name];
}
}