在前端,白屏是指页面无法正常显示内容,整个页面呈现一片空白的情况,通常是由于代码错误、资源加载失败(比如服务端异常)等原因引起的。
最开始我们采用的解决方案是,当接口状态为500或502时,直接将用户重定向到登录页面。这种做法虽然在服务端稳定的情况下能够快速解决问题,但后来在测试中发现,如果页面中某个接口请求失败,也会直接跳转到登录页面。这不仅对用户体验非常不友好,而且我们无法对所有接口进行区分,明确哪些接口异常应该返回登录页面,哪些不应该返回。因此,我们需要一种更精细化的处理方式,既能有效应对服务端异常,又能避免对用户造成不必要的干扰。
解决办法
在入口文件app.vue文件内增加白屏检测方法,如果白屏持续15s退回登录页面
具体怎么实现呢,我们可以采用页面采样识别的方法:
- 页面加载完成后,检测屏幕中间及中间偏左右上下的18个点(垂直采样)
- 如果这些点上的元素都是指定的“空白”容器(比如 html、body、#app、#root),说明页面没有渲染出内容,可能是白屏
- 连续检测超过5次仍然白屏,则判定为白屏
注意:整体性能影响可以忽略不计,只有在检测到白屏时才会启动定时器,正常页面不会持续检测。定时器间隔是3秒,最多检测5次,检测总时长最多15秒。而且elementsFromPoint 是浏览器原生API,性能开销较低。
function whiteScreenMonitor(wrapperSelectors: any[]) {
// 记录检测到空白点的数量
let emptyPoints = 0;
// 定时器变量,用于重复检测
let timer = null;
// 最大重试次数,超过则判定为白屏
const maxRetryTime = 5;
// 当前重试次数
let retryTime = 1;
// 根据元素获取对应的选择器字符串
function getSelector(element) {
if (element?.id) {
// 如果元素有id,返回id选择器
return `#${element.id}`;
}
if (element?.className) {
// 如果有class,返回类选择器,多个class用点连接
return `.${element.className.split(' ').filter(item => !!item).join('.')}`;
}
// 否则返回标签名(小写)
return element?.nodeName.toLowerCase();
}
// 判断元素是否是“空白”容器,即是否在传入的wrapperSelectors列表中
function isWrapper(element) {
const selector = getSelector(element);
return wrapperSelectors.includes(selector);
}
// 核心检测函数,检测页面多个点是否都是空白容器
checkBlankScreen = function () {
emptyPoints = 0; // 每次检测前清零计数
// 检测横向9个点(屏幕宽度的10%到90%),纵向中间点
for (let i = 1; i <= 9; i++) {
// 获取横向点的元素列表,取第一个元素
const xElements = document.elementsFromPoint((window.innerWidth * i) / 10, window.innerHeight / 2);
// 获取纵向点的元素列表,取第一个元素
const yElements = document.elementsFromPoint(window.innerWidth / 2, (window.innerHeight * i) / 10);
// 如果横向点第一个元素是空白容器,计数+1
emptyPoints += isWrapper(xElements[0]) ? 1 : 0;
// 如果纵向点第一个元素是空白容器,计数+1
emptyPoints += isWrapper(yElements[0]) ? 1 : 0;
}
// 如果空白点数量超过15(满分18),说明大部分点都是空白容器
console.log("emptyPoints", emptyPoints);
if (emptyPoints >= 15) {
console.log(`这是第 ${retryTime} 次检测,页面白屏了`);
// 重试次数+1
if (++retryTime > maxRetryTime) {
// 超过最大重试次数,判定为白屏,停止检测
console.log('页面白屏检测超过最大次数,可判定为白屏');
clearInterval(timer);
timer = null;
// 退出登录
handleUnLogin();
return;
}
// 防止重复启动定时器 如果定时器未启动,启动定时器每秒检测一次
if (!timer) {
timer = setInterval(checkBlankScreen, 3000);
}
} else {
// 空白点不多,说明页面正常,清除定时器停止检测
clearInterval(timer);
timer = null;
retryTime = 1; // 重置重试次数
}
}
// 页面加载完成后执行一次检测
window.addEventListener('load', checkBlankScreen);
}
whiteScreenMonitor(['html', 'body', '#app', '#root']);
onBeforeUnmount(async () => {
window.removeEventListener('load', checkBlankScreen);
});