白屏的表现与原因
白屏的通常表现为:
- 页面空白或仅显示背景色,没有实际内容
- 页面一直展示骨架屏,包括页面 loading 状态
- 页面只展示导航菜单,内容区空白,包括微前端或 iframe 嵌套子页面的场景
导致白屏的原因分两种:资源加载错误、代码执行错误。
检测方案对比
通过以上对比发现,采用「页面关键点采样对比」的实现方案较好。
需要注意的是,对于主应用内嵌入的 iframe 的场景,因为每次采样取到的都是整个 iframe 元素,所以无法在主应用侧判断 iframe 是否白屏,需要在 iframe 应用内接入白屏检测 SDK。
流程图
数据采集
屏幕采样点选取
采样点的选取有三种方式:垂直采样、交叉采样、垂直交叉采样。
垂直采样
垂直采样时屏幕的采样点坐标
交叉采样
交叉采样时屏幕的采样点坐标
垂直交叉采样
垂直交叉采样时屏幕的采样点坐标
很明显,采样点越多判断越准确,但计算量稍大一点,不过我们利用 requestIdleCallback 在浏览器空闲时计算。因此,我们选择垂直交叉的采样方式。
白屏的判断标准与检测时机
有骨架屏和无骨架屏应用的检测方式不一样,检测时机也有细微差别。
无骨架屏场景
检测时机
document.readyState在 complete 时或 load 事件触发时- 全局 error 事件触发时
- 全局 unhandledrejection 事件触发时
检测方式
初始化 SDK 时,我们需要配置哪些是根容器,如果根容器为空则说明是白屏。
具体实现方式为,根据屏幕的宽度(window.innerWidth)和高度(window.innerHeight)算出每个采样点的具体坐标,再用 elementsFromPoint 获取每个坐标的 dom 元素,对比获取的元素是否为配置的根容器元素。
【第3057期】elementFromPoint:获取页面上特定点的元素
仔细想一下,上面的判断方式其实会有问题。
因为在微前端与 iframe 场景下,子应用白屏时,应该也需要上报才对。如果按上述方式判断,主应用(一般包含导航或者一级菜单)如果没有白屏,子应用永远不会被检测出白屏。因此,需要兼容此类场景。
兼容方式也很简单,我们只要判断内容区内的采样点满足白屏条件即可。大部分后台类的应用,会有顶部导航或左侧的一级菜单,因此我们选定右下方为内容区。
内容区内的采样点
如上图所示,整个屏幕共 33 个采样点,其中内容区有 28 个。简单起见,检测白屏时,我们判断空白的采样点是否大于等于 28 个。采样点坐标的获取如下:
for (let i = 1; i <= 9; i++) {
// x轴采样点
const xElements = document?.elementsFromPoint((window.innerWidth * i) / 10, window.innerHeight / 2);
// y轴采样点
const yElements = document?.elementsFromPoint(window.innerWidth / 2, (window.innerHeight * i) / 10);
// 上升的对角线采样点
const upDiagonalElements = document?.elementsFromPoint(
(window.innerWidth * i) / 10,
(window.innerHeight * i) / 10,
);
// 下降的对角线采样点
const downDiagonalElements = document?.elementsFromPoint(
(window.innerWidth * i) / 10,
window.innerHeight - (window.innerHeight * i) / 10,
);
if (this.isContainer(xElements[0] as HTMLElement)) emptyPoints++;
// 中心点只计算一次
if (i !== 5) {
if (this.isContainer(yElements[0] as HTMLElement)) emptyPoints++;
if (this.isContainer(upDiagonalElements[0] as HTMLElement)) emptyPoints++;
if (this.isContainer(downDiagonalElements[0] as HTMLElement)) emptyPoints++;
}
}
有骨架屏场景
检测时机
document.readyState在 complete 之前- 全局 error 事件触发时
- 全局 unhandledrejection 事件触发时
检测方式
如果应用内有骨架屏,继续用无骨架屏应用的白屏检测方式已经无法判断白屏,因为骨架屏也是有效的 dom 元素。
有骨架屏应用的检测方式为:对比初次采样前后获取的 dom 元素是否一致。因为在页面加载完成前可能已经渲染完骨架屏,为了获取对照组数据,初次采样的时间要在页面加载完成前。
// 项目有骨架屏
if (this.isSkeletonApp) {
if (document.readyState !== 'complete') {
this.idleCallback({
type: 'beforeComplete',
message: '骨架屏场景白屏',
});
}
} else {
// 页面加载完毕
if (document.readyState === 'complete') {
this.idleCallback({
type: 'complete',
message: '页面加载完毕白屏',
});
} else {
window.addEventListener(
'load',
this.idleCallback.bind(this, {
type: 'load',
message: '页面加载完毕白屏',
}),
);
}
}
window.addEventListener('error', (e) => {
this.idleCallback({
type: e.type,
message: e.message,
filename: e.filename,
lineno: e.lineno,
colno: e.colno,
});
});
window.addEventListener('unhandledrejection', (e) => {
this.idleCallback({
type: e.type,
reason: e.reason,
message: 'Promise未捕获的错误',
});
});
数据上报
检测出白屏问题后,就要上报白屏信息到数据后台了。一般数据后台需要有数据清洗、存储、消费、告警等功能。此外,还需要区分不同的产品与环境,控制上报数据并发量、上报用户浏览器信息、用户行为数据、方便排查问题的 Sourcemap,告警方式与规则等细节问题。如果要将数据后台做的全面细致,实现成本是比较高的。
权衡投入产出比后,我们的数据后台复用了云音乐部门同事研发的前端错误监控平台 Corona。我们要做的就是将上报白屏错误到 Corona 的逻辑,内置到白屏检测 SDK 中。
SDK 的接入方式
SDK 支持以外链方式接入前端应用,除云音乐的 Corona puzzle 脚本外,不依赖其他资源加载,一般只需要改动模板文件,不侵入业务代码。
SDK API
配置项
方法
通过外链接入
只需要在模板文件内,通过外链 script 引入白屏检测 SDK 及其依赖的 Corona SDK 即可。
【第2984期】搭建自动化 Web 页面性能检测系统 —— 实现篇
总结
本文首先介绍了前端白屏表现、白屏原因,以及修复白屏问题的业务价值。然后对比几种常见的白屏检测方案,并介绍了采样点检测方案的具体实现,包括采样点如何选取、白屏的判断标准与检测时机、微前端与 iframe 场景的兼容等。
白屏检测工具完善现有的质量保障体系。使我们能尽早发现、及时处理白屏问题,减少线上重大故障几率,降低白屏问题对客户的负面影响。下一篇文章将介绍如何自动化的检测白屏,模拟用户的行为,主动发现问题。