Skip to content

白屏检测机制解析:如何精准识别页面“白屏”异常

在现代前端监控体系中,白屏检测(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.OKERROR
  • 低性能开销运行,不影响用户体验

二、关键参数说明

ts
export function openWhiteScreen(
  callback: Callback,
  { skeletonProject, whiteBoxElements }: InitOptions
);
参数类型说明
callbackFunction检测结果回调函数,用于上报状态
skeletonProjectboolean是否使用骨架屏(影响判断逻辑)
whiteBoxElementsstring[]被视为“容器”的选择器列表,默认为 ['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 的旧浏览器