白屏骨架屏监控
2020-12-19 本文已影响0人
Roseska
调研过程
- 发现了几个有意思的api
- Document.elementsFromPoint():可以获取到当前视口内指定坐标处,由里到外排列的所有元素
-
HTMLElement.innerText:
innerText
很容易与Node.textContent
混淆, 但这两个属性间实际上有很重要的区别. 大体来说,innerText
可操作已被渲染的内容, 而textContent
则不会 -
Node.nodeType:只读属性
Node.nodeType
表示的是该节点的类型 - Document.getElementsByClassName():返回一个包含了所有指定类名的子元素的类数组对象
-
CanvasRenderingContext2D.measureText():返回一个关于被测量文本
TextMetrics
对象包含的信息(例如它的宽度)let ctx = this.container.getContext('2d'); // canvas 上下文 let width= ctx.measureText(name).width;
- 前端监控几个方法
-
如果每次都去遍历新增元素并计算是否可见是非常消耗性能的。实际上采用的是深度优先算法,如果子元素可见,那父元素可见,不再计算。 同样的,如果最后一个元素可见,那前面的兄弟元素也可见。通过深度优先算法,性能有了大幅的提升
-
mutationObserver
:通过这个API
可以获得页面状态变化 -
加载白屏时间可以通过
first meaningful paint
来判定,(first meaning paint
, 也就是主要内容可见时间) -
Hook
宿主方APP
里所有UIWebView
的delegate
事件,获取网页加载结束的时机。截取网页图像,然后将完整的图像压缩成一定比例,一般取横向7个像素,纵向根据原比例得出,比如原网页视图700*1000
,压缩之后的图像大小:7*10
。遍历图像上的像素,是否全为白色点,以此确定网页是否白屏,埋点上报
我的思路:
- 找到灰色区域,并统计面积和个数。可参考回溯算法:用 canvas 的 getImageData 做点有趣的事
以下是判断白屏、骨架屏方法:
- 首先调查下目前的骨架屏方案有哪些,如果都是
dom
节点,就遍历所有的dom
判断里边是否有文字,由于这种方案会同时判断白屏,可以通过形状来把白屏和骨架屏区分开,或者判断不是白屏就是骨架屏
注意点:
- 有的页面顶部有走马灯,这样可能会影响骨架屏的判断
- 有的插件引入后会中带有默认文字,这样会影响骨架屏的判断,可以动态传入页面id来解决
let root = typeof self == 'object' && self.self === self && self || typeof global == 'object' && global.global === global && global || window || {};
setTimeout(() => {
skeletonscreen()
}, 5000);
// 递归函数,获取页面dom数组
function countNodes (node, deepArr) {
node = node || document.body
// 计算自身
let arr = deepArr ? deepArr : []
// 判断是否存在子节点
if (node.hasChildNodes()) {
// 获取子节点
var cnodes = node.childNodes;
// 对子节点进行递归统计
for (var i = 0; i < cnodes.length; i++) {
countNodes(cnodes.item(i), arr)
}
} else {
arr.push(node)
}
return arr;
}
// 统计body的节点
let nodesArr = countNodes()
console.log('nodesArr', nodesArr)
let isNoTxt = nodesArr.some((item, index) => {
return item.nodeValue && item.nodeValue.replace(/\s*/g, "")
})
let seletonseletonCoordinates = []
//计算每个色块的面积
const linkSum = (i, j, num) => {
//走过的路就置0
coordinates[i][j] = 0;
num++;
//向上
if ((i + 1 < h) && coordinates[i + 1][j] == 1) {
num = linkSum(i + 1, j, num);
}
//向右
if ((j + 1 < w) && coordinates[i][j + 1] == 1) {
num = linkSum(i, j + 1, num);
}
//向下
if ((i - 1 >= 0) && coordinates[i - 1][j] == 1) {
num = linkSum(i - 1, j, num);
}
//向左
if ((j - 1 >= 0) && coordinates[i][j - 1] == 1) {
num = linkSum(i, j - 1, num);
}
return num;
}
//计算总块数和灰色区域总面积
const getCountAndArea = () => {
let sum = [];
let count = 0;
for (let i = 0; i < h; i++) {
for (let j = 0; j < w; j++) {
//连续1的个数
if (seletonCoordinates[i][j] == 1) {
let buf = 0;
buf = linkSum(i, j, buf);
count++;
sum.push({
index: count,
area: buf
});
}
}
}
return {
count,
sum
};
}
//根据颜色判断
function skeletonscreen () {
if (root.html2canvas) {
html2canvas(document.body, {
backgroundColor: null, //设置截图的背景色
useCORS: true, // 如果截图的内容里有图片,可能会有跨域的情况,加上这个参数,解决文件跨域问题
allowTaint: false, //允许跨域(图片跨域相关)
taintTest: true, //是否在渲染前测试图片
}).then((canvas) => {
let ctx = canvas.getContext("2d");
const ratio = root.devicePixelRatio
const width = document.body.clientWidth * ratio
const height = document.body.clientHeight * ratio / 3
let img = ctx.getImageData(0, 0, width, height);
let sumPx = 0
const imgdata = img.data
let r, g, b
let x = 0, y = 0
//设置二维数组
for (let i = 0; i < height; i++) {
seletonCoordinates[i] = []
}
for (var i = 0; i < imgdata.length; i += 4) {
r = imgdata[i];
g = imgdata[i + 1];
b = imgdata[i + 2];
if (r === 255 && g === 255 && b === 255) {
sumPx += 1
seletonCoordinates[y][x] = 0
} else {
seletonCoordinates[y][x] = 1
}
x++
if (x > width) {
x = 0
y++
}
}
let rst = getCountAndArea();
if (rst.count > 3 && !isNoTxt) {
console.log('页面骨架屏')
}
})
}
}