前端白屏监测

83 阅读3分钟

在前端,白屏是指页面无法正常显示内容,整个页面呈现一片空白的情况,通常是由于代码错误、资源加载失败(比如服务端异常)等原因引起的。

最开始我们采用的解决方案是,当接口状态为500或502时,直接将用户重定向到登录页面。这种做法虽然在服务端稳定的情况下能够快速解决问题,但后来在测试中发现,如果页面中某个接口请求失败,也会直接跳转到登录页面。这不仅对用户体验非常不友好,而且我们无法对所有接口进行区分,明确哪些接口异常应该返回登录页面,哪些不应该返回。因此,我们需要一种更精细化的处理方式,既能有效应对服务端异常,又能避免对用户造成不必要的干扰。

解决办法

在入口文件app.vue文件内增加白屏检测方法,如果白屏持续15s退回登录页面

具体怎么实现呢,我们可以采用页面采样识别的方法:

  1. 页面加载完成后,检测屏幕中间及中间偏左右上下的18个点(垂直采样)
  2. 如果这些点上的元素都是指定的“空白”容器(比如 html、body、#app、#root),说明页面没有渲染出内容,可能是白屏
  3. 连续检测超过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);
    });