白屏检测机制解析:如何精准识别页面“白屏”异常
在现代前端监控体系中,白屏检测(White Screen Detection) 是一项关键能力。它用于判断用户是否看到一片空白页面——这通常意味着首屏渲染失败、资源加载异常或 JavaScript 阻塞等问题。本文将深入剖析一段典型的白屏检测实现代码,梳理其设计思路、执行流程与潜在优化点。
检测函数
js
/**
* 检测页面是否白屏
* @param {function} callback - 回调函数获取检测结果(上报检测值)
* @param {boolean} skeletonProject - 页面是否有骨架屏
* @param {array} whiteBoxElements - 容器列表,默认值为['html', 'body', '#app', '#root']
*/
export function openWhiteScreen(
callback: Callback,
{ skeletonProject, whiteBoxElements }: InitOptions
) {
let _whiteLoopNum = 0;
const _skeletonInitList: any = []; // 存储初次采样点
let _skeletonNowList: any = []; // 存储当前采样点
// 项目有骨架屏
if (skeletonProject) {
if (document.readyState != 'complete') {
idleCallback();
}
} else {
// 页面加载完毕
if (document.readyState === 'complete') {
idleCallback();
} else {
_global.addEventListener('load', idleCallback);
}
}
// 选中dom点的名称
function getSelector(element: any) {
if (element.id) {
return '#' + element.id;
} else if (element.className) {
// div home => div.home
return (
'.' +
element.className
.split(' ')
.filter((item: any) => !!item)
.join('.')
);
} else {
return element.nodeName.toLowerCase();
}
}
// 判断采样点是否为容器节点
function isContainer(element: HTMLElement) {
const selector = getSelector(element);
if (skeletonProject) {
_whiteLoopNum ? _skeletonNowList.push(selector) : _skeletonInitList.push(selector);
}
return whiteBoxElements?.indexOf(selector) != -1;
}
// 采样对比
function sampling() {
let emptyPoints = 0;
for (let i = 1; i <= 9; i++) {
const xElements = document.elementsFromPoint(
(_global.innerWidth * i) / 10,
_global.innerHeight / 2
);
const yElements = document.elementsFromPoint(
_global.innerWidth / 2,
(_global.innerHeight * i) / 10
);
if (isContainer(xElements[0] as HTMLElement)) emptyPoints++;
// 中心点只计算一次
if (i != 5) {
if (isContainer(yElements[0] as HTMLElement)) emptyPoints++;
}
}
// 页面正常渲染,停止轮训
if (emptyPoints != 17) {
if (skeletonProject) {
// 第一次不比较
if (!_whiteLoopNum) return openWhiteLoop();
// 比较前后dom是否一致
if (_skeletonNowList.join() === _skeletonInitList.join())
return callback({
status: STATUS_CODE.ERROR
});
}
if (_support._loopTimer) {
clearInterval(_support._loopTimer);
_support._loopTimer = null;
}
} else {
// 开启轮训
if (!_support._loopTimer) {
openWhiteLoop();
}
}
// 17个点都是容器节点算作白屏
callback({
status: emptyPoints == 17 ? STATUS_CODE.ERROR : STATUS_CODE.OK
});
}
// 开启白屏轮训
function openWhiteLoop(): void {
if (_support._loopTimer) return;
_support._loopTimer = setInterval(() => {
if (skeletonProject) {
_whiteLoopNum++;
_skeletonNowList = [];
}
idleCallback();
}, 1000);
}
function idleCallback() {
// 判断浏览器是否支持requestIdleCallback
if ('requestIdleCallback' in _global) {
requestIdleCallback(deadline => {
// timeRemaining:表示当前空闲时间的剩余时间
if (deadline.timeRemaining() > 0) {
sampling();
}
});
} else {
sampling();
}
}
}一、核心目标
该 openWhiteScreen 函数旨在:
- 自动检测页面是否处于白屏状态
- 区分“骨架屏”与“真实白屏”,避免误报
- 通过回调上报检测结果(
STATUS_CODE.OK或ERROR) - 低性能开销运行,不影响用户体验
二、关键参数说明
ts
export function openWhiteScreen(
callback: Callback,
{ skeletonProject, whiteBoxElements }: InitOptions
);| 参数 | 类型 | 说明 |
|---|---|---|
callback | Function | 检测结果回调函数,用于上报状态 |
skeletonProject | boolean | 是否使用骨架屏(影响判断逻辑) |
whiteBoxElements | string[] | 被视为“容器”的选择器列表,默认为 ['html', 'body', '#app', '#root'] |
白屏定义:当页面上多个关键采样点的顶层元素均为“容器节点”(无实际内容),则判定为白屏。
三、整体执行流程
1. 启动时机控制
- 有骨架屏,在
document.readyState !== 'complete'页面未加载完成时,就开始检测页面点位元素,记录起初页面元素,便于后续对比。如果相同则判定为白屏。 - 无骨架屏,在
document.readyState === 'complete'页面加载完成时,开始检测页面点位元素是否为容器节点,如果是,则判定为白屏。
2. DOM 元素标识生成 —— getSelector
为便于比较,将 DOM 元素转换为简易 CSS 选择器:
ts
function getSelector(element) {
if (element.id) return "#" + element.id;
if (element.className)
return "." + className.split(" ").filter(Boolean).join(".");
return element.nodeName.toLowerCase();
}示例:
<div id="app">→#app<div class="main page">→.main.page<span>→span
⚠️ 局限:无法唯一标识无 ID/Class 的同类型元素,但在白屏检测场景中可接受。
3. 容器节点判断 —— isContainer
ts
function isContainer(element: HTMLElement): boolean {
const selector = getSelector(element);
// 骨架屏模式下记录采样点
if (skeletonProject) {
_whiteLoopNum
? _skeletonNowList.push(selector)
: _skeletonInitList.push(selector);
}
// 判断是否在白名单容器中
return whiteBoxElements?.indexOf(selector) !== -1;
}- 若元素选择器在
whiteBoxElements中,则视为“空白容器”。 - 骨架屏模式下,同时记录当前采样点的选择器,用于后续对比。
4. 核心采样逻辑 —— sampling
采样策略:17 个屏幕关键点
- 横向 9 点:x = 10% ~ 90% 屏宽,y = 50% 屏高
- 纵向 9 点:x = 50% 屏宽,y = 10% ~ 90% 屏高
- 中心点 (50%,50%) 重复,仅计一次 → 共 17 个点
使用 document.elementsFromPoint(x, y) 获取每个坐标下的最上层可见元素。
白屏判定规则
- 若 17 个点的顶层元素全是容器节点 → 判定为白屏(
emptyPoints === 17) - 否则 → 页面正常渲染
骨架屏特殊处理
- 第 0 次采样(初始状态):仅记录 DOM 结构(
_skeletonInitList),不判断 - 后续采样:比较当前结构(
_skeletonNowList)与初始结构- 若一致 → 页面卡在骨架屏,未更新内容 → 真实白屏
- 若不一致 → 内容已渲染 → 正常
✅ 此设计有效避免将“正在加载的骨架屏”误判为“故障白屏”。
5. 轮询与性能优化
自适应轮询机制
- 首次检测为白屏 → 启动每秒一次的轮询(
setInterval) - 一旦检测到非白屏 → 立即清除定时器,停止轮询
- 使用
_support._loopTimer防止重复启动
空闲执行策略
ts
function idleCallback() {
if ("requestIdleCallback" in window) {
requestIdleCallback(() => sampling());
} else {
sampling(); // 降级方案
}
}- 优先利用浏览器空闲时间执行检测,最小化对主线程的干扰
- 兼容不支持
requestIdleCallback的旧浏览器
