前端白屏检测:SDK的设计与实现

4,746 阅读7分钟

前言

前端白屏指页面在加载过程中长时间无法正常展示内容,内容区空白,使用户无法进行查看、保存等一切操作,这是非常严重的问题。如果能尽早检测到白屏问题,就可以及时处理,避免或降低负面影响。

白屏的检测手段有两种。一是真实用户端的检测,通过接入白屏检测SDK实现,无法在用户端白屏报错前发现问题,是被动监控的方式;另一种是自动化检测,在团队内部通过自动化工具模拟用户行为主动检测,可以提前发现问题。

本文为前端白屏检测的上篇,主要讲真实用户端的检测,即SDK的设计与实现。

白屏的表现与原因

白屏的通常表现为:

  1. 页面空白或仅显示背景色,没有实际内容

  2. 页面一直展示骨架屏,包括页面loading状态

  3. 页面只展示导航菜单,内容区空白,包括微前端或iframe嵌套子页面的场景

导致白屏的原因分两种:资源加载错误、代码执行错误。

检测方案对比

方案原理优点缺点
检测根节点是否渲染SPA框架渲染的 DOM 一般挂载在一个根节点下,监听onload、onerror事件,检测根节点下是否挂载 DOM开发成本低通用性差,只兼容主流 SPA 框架
监听 DOM 变化利用 Mutation Observer API 监听DOM变化开发成本低准确度低,无法检测未渲染、始终渲染骨架屏等情况,如果用户长时间未操作DOM可能会误判白屏
页面截图对比对页面截图,将截图与纯白的图片做对比技术栈无关,通用性好准确度低,无法检测纯背景色、骨架屏的白屏场景
前端框架内置ErrorBoundary组件捕获异常利用ErrorBoundary组件捕获JS执行异常检测白屏开发成本低无法检测资源异常导致的白屏,只兼容于特定框架应用,接入时对业务代码侵入大
页面关键点采样对比在页面中垂直/交叉取多个采样点,用 elementsFromPoint API 获取采样点下的 HTML 元素,判断采样点元素是否与容器元素相同准确度高,技术栈无关,通用性好开发成本稍高

通过以上对比发现,采用「页面关键点采样对比」的实现方案较好。

需要注意的是,对于主应用内嵌入的iframe的场景,因为每次采样取到的都是整个iframe元素,所以无法在主应用侧判断iframe是否白屏,需要在iframe应用内接入白屏检测SDK。

流程图

白屏检测流程图.png

数据采集

屏幕采样点选取

采样点的选取有三种方式:垂直采样、交叉采样、垂直交叉采样。

垂直采样

垂直采样.png

交叉采样

交叉采样.png

垂直交叉采样

垂直交叉采样.png

很明显,采样点越多判断越准确,但计算量稍大一点,不过我们利用requestIdleCallback在浏览器空闲时计算。因此,我们选择垂直交叉的采样方式。

白屏的判断标准与检测时机

有骨架屏和无骨架屏应用的检测方式不一样,检测时机也有细微差别。

无骨架屏场景

检测时机

  1. document.readyState在complete时或load事件触发时
  2. 全局error事件触发时
  3. 全局unhandledrejection事件触发时

检测方式

初始化SDK时,我们需要配置哪些是根容器,如果根容器为空则说明是白屏。

具体实现方式为,根据屏幕的宽度(window.innerWidth)和高度(window.innerHeight)算出每个采样点的具体坐标,再用elementsFromPoint获取每个坐标的 dom 元素,对比获取的元素是否为配置的根容器元素。

仔细想一下,上面的判断方式其实会有问题。

因为在微前端与iframe场景下,子应用白屏时,应该也需要上报才对。如果按上述方式判断,主应用(一般包含导航或者一级菜单)如果没有白屏,子应用永远不会被检测出白屏。因此,需要兼容此类场景。

兼容方式也很简单,我们只要判断内容区内的采样点满足白屏条件即可。大部分后台类的应用,会有顶部导航或左侧的一级菜单,因此我们选定右下方为内容区。

内容区采样.png

如上图所示,整个屏幕共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++;
  }
}

有骨架屏场景

检测时机

  1. document.readyState在complete之前

  2. 全局error事件触发时

  3. 全局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

配置项

字段名类型说明是否必须默认值
containersString[]需要检测白屏的容器选择器列表['html', 'body', '#app', '#root', '#mainapp-container', '#subapp-container']
coronaCoronaCorona平台错误监控的实例window.corona
isSkeletonAppBoolean是否是有骨架屏的应用false
autoInitBoolean是否自动初始化SDKtrue
debugBoolean是否开启调试模式,开启后会打印日志false

方法

方法名说明参数返回值
init初始化SDK

通过外链接入

只需要在模板文件内,通过外链script引入白屏检测SDK及其依赖的Corona SDK即可。

总结

本文首先介绍了前端白屏表现、白屏原因,以及修复白屏问题的业务价值。然后对比几种常见的白屏检测方案,并介绍了采样点检测方案的具体实现,包括采样点如何选取、白屏的判断标准与检测时机、微前端与iframe场景的兼容等。

白屏检测工具完善现有的质量保障体系。使我们能尽早发现、及时处理白屏问题,减少线上重大故障几率,降低白屏问题对客户的负面影响。下一篇文章将介绍如何自动化的检测白屏,模拟用户的行为,主动发现问题。