前端白屏的检测方案,让你知道自己的页面白了

news2025/7/13 0:44:44

前言

页面白屏,绝对是让前端开发者最为胆寒的事情,特别是随着 SPA 项目的盛行,前端白屏的情况变得更为复杂且棘手起来( 这里的白屏是指页面一直处于白屏状态 )
要是能检测到页面白屏就太棒了,开发者谁都不想成为最后一个知道自己页面白的人😥
web-see 前端监控方案,提供了 采样对比+白屏修正机制 的检测方案,兼容有骨架屏、无骨架屏这两种情况,来解决开发者的白屏之忧

知道页面白了,然后呢?

web-see 前端监控,会给每次页面访问生成一个唯一的uuid,当上报页面白屏后,开发者可以根据白屏的uuid,去监控后台查询该id下对应的代码报错、资源报错等信息,定位到具体的源码,帮助开发者快速解决白屏问题

白屏检测方案的实现流程

采样对比+白屏修正机制的主要流程:
1、页面中间取17个采样点(如下图),利用 elementsFromPoint api 获取该坐标点下的 HTML 元素
2、定义属于容器元素的集合,如

[‘html’, ‘body’, ‘#app’, ‘#root’]

3、判断17这个采样点是否在该容器集合中。说白了,就是判断采样点有没有内容;如果没有内容,该点的 dom 元素还是容器元素,若17个采样点都没有内容则算作白屏
4、若初次判断是白屏,开启轮询检测,来确保白屏检测结果的正确性,直到页面的正常渲染
采样点分布图(蓝色为采样点):
在这里插入图片描述

如何使用

import webSee from 'web-see';

Vue.use(webSee, {
  dsn: 'http://localhost:8083/reportData', // 上报的地址
  apikey: 'project1', // 项目唯一的id
  userId: '89757', // 用户id
  silentWhiteScreen: true, // 开启白屏检测
  skeletonProject: true, // 项目是否有骨架屏
  whiteBoxElements: ['html', 'body', '#app', '#root'] // 白屏检测的容器列表
});

下面聊一聊具体的分析与实现

白屏检测的难点

1) 白屏原因的不确定

从问题推导现象虽然能成功,但从现象去推导问题却走不通。白屏发生时,无法和具体某个报错联系起来,也可能根本没有报错,比如关键资源还没有加载完成
导致白屏的原因,大致分两种:资源加载错误、代码执行错误
2) 前端渲染方式的多样性
前端页面渲染方式有多种,比如 客户端渲染 CSR 、服务端渲染 SSR 、静态页面生成 SSG 等,每种模式各不相同,白屏发生的情况也不尽相同
很难用一种统一的标准去判断页面是否白了

技术方案调研

如何设计出一种,在准确性、通用型、易用性等方面均表现良好的检测方案呢?
本文主要讨论 SPA 项目的白屏检测方案,包括有无骨架屏的两种情况
方案一:检测根节点是否渲染
原理很简单,在当前主流 SPA 框架下,DOM 一般挂载在一个根节点之下(比如 <div id="app"></div> ),发生白屏后通常是根节点下所有 DOM 被卸载,该方法通过检测根节点下是否挂载 DOM,若无则证明白屏
这是简单明了且有效的方案,但缺点也很明显:其一切建立在 白屏 === 根节点下 DOM 被卸载 成立的前提下,缺点是通用性较差,对于有骨架屏的情况束手无策
方案二:Mutation Observer 监听 DOM 变化
通过此 API 监听页面 DOM 变化,并告诉我们每次变化的 DOM 是被增加还是删除
但这个方案有几个缺陷
1)白屏不一定是 DOM 被卸载,也有可能是压根没渲染,且正常情况也有可能大量 DOM 被卸载
2)遇到有骨架屏的项目,若页面从始至终就没变化,一直显示骨架屏,这种情况 Mutation Observer 也束手无策
方案三:页面截图检测
这种方式是基于原生图片对比算法处理白屏检测的 web 实现
整体流程:对页面进行截图,将截图与一张纯白的图片做对比,判断两者是否足够相似
但这个方案有几个缺陷:
1、方案较为复杂,性能不高;一方面需要借助 canvas 实现前端截屏,同时需要借助复杂的算法对图片进行对比
2、通用性较差,对于有骨架屏的项目,对比的样张要由纯白的图片替换成骨架屏的截图
方案四:采样对比
该方法是对页面取关键点,进行采样对比,在准确性、易用性等方面均表现良好,也是最终采用的方案

对于有骨架屏的项目,通过对比前后获取的 dom 元素是否一致,来判断页面是否变化(这块后面专门讲解)

采样对比代码:

// 监听页面白屏
function whiteScreen() {
  // 页面加载完毕
  function onload(callback) {
    if (document.readyState === 'complete') {
      callback();
    } else {
      window.addEventListener('load', callback);
    }
  }
  // 定义外层容器元素的集合
  let containerElements = ['html', 'body', '#app', '#root'];
  // 容器元素个数
  let emptyPoints = 0;
  // 选中dom的名称
  function getSelector(element) {
    if (element.id) {
      return "#" + element.id;
    } else if (element.className) {// div home => div.home
      return "." + element.className.split(' ').filter(item => !!item).join('.');
    } else {
      return element.nodeName.toLowerCase();
    }
  }
  // 是否为容器节点
  function isContainer(element) {
    let selector = getSelector(element);
    if (containerElements.indexOf(selector) != -1) {
      emptyPoints++;
    }
  }
  onload(() => {
    // 页面加载完毕初始化
    for (let i = 1; i <= 9; i++) {
      let xElements = document.elementsFromPoint(window.innerWidth * i / 10, window.innerHeight / 2);
      let yElements = document.elementsFromPoint(window.innerWidth / 2, window.innerHeight * i / 10);
      isContainer(xElements[0]);
      // 中心点只计算一次
      if (i != 5) {
        isContainer(yElements[0]);
      }
    }
    // 17个点都是容器节点算作白屏
    if (emptyPoints == 17) {
      // 获取白屏信息
      console.log({
        status: 'error'
      });
    }
  }
}

白屏修正机制
若首次检测页面为白屏后,任务还没有完成,特别是手机端的项目,有可能是用户网络环境不好,关键的JS资源或接口请求还没有返回,导致的页面白屏
需要使用轮询检测,来确保白屏检测结果的正确性,直到页面的正常渲染,这就是白屏修正机制
白屏修正机制图例:
在这里插入图片描述
轮询代码:

// 采样对比
function sampling() {
  let emptyPoints = 0;
  ……
  // 页面正常渲染,停止轮询
  if (emptyPoints != 17) {
    if (window.whiteLoopTimer) {
      clearTimeout(window.whiteLoopTimer)
      window.whiteLoopTimer = null
    }
  } else {
    // 开启轮询
    if (!window.whiteLoopTimer) {
      whiteLoop()
    }
  }
  // 通过轮询不断修改之前的检测结果,直到页面正常渲染
  console.log({
    status: emptyPoints == 17 ? 'error' : 'ok'
  });
}
// 白屏轮询
function whiteLoop() {
  window.whiteLoopTimer = setInterval(() => {
    sampling()
  }, 1000)
}

骨架屏

对于有骨架屏的页面,用户打开页面后,先看到骨架屏,然后再显示正常的页面,来提升用户体验;但如果页面从始至终都显示骨架屏,也算是白屏的一种

骨架屏示例:
在这里插入图片描述
骨架屏的原理
无论 vue 还是 react,页面内容都是挂载到根节点上。常见的骨架屏插件,就是基于这种原理,在项目打包时将骨架屏的内容直接放到 html 文件的根节点中

有骨架屏的html文件:
在这里插入图片描述
骨架屏的白屏检测
上面的白屏检测方案对有骨架屏的项目失灵了,虽然页面一直显示骨架屏,但判断结果页面不是白屏,不符合我们的预期
需要通过外部传参明确的告诉 SDK,该页面是不是有骨架屏,如果有骨架屏,通过对比前后获取的 dom 元素是否一致,来实现骨架屏的白屏检测
完整代码:

/**
 * 检测页面是否白屏
 * @param {function} callback - 回到函数获取检测结果
 * @param {boolean} skeletonProject - 页面是否有骨架屏
 * @param {array} whiteBoxElements - 容器列表,默认值为['html', 'body', '#app', '#root']
 */
export function openWhiteScreen(callback, { skeletonProject, whiteBoxElements }) {
  let _whiteLoopNum = 0;
  let _skeletonInitList = []; // 存储初次采样点
  let _skeletonNowList = []; // 存储当前采样点

  // 项目有骨架屏
  if (skeletonProject) {
    if (document.readyState != 'complete') {
      sampling();
    }
  } else {
    // 页面加载完毕
    if (document.readyState === 'complete') {
      sampling();
    } else {
      window.addEventListener('load', sampling);
    }
  }
  // 选中dom点的名称
  function getSelector(element) {
    if (element.id) {
      return '#' + element.id;
    } else if (element.className) {
      // div home => div.home
      return ('.' + element.className.split(' ').filter(item => !!item).join('.'));
    } else {
      return element.nodeName.toLowerCase();
    }
  }
  // 判断采样点是否为容器节点
  function isContainer(element) {
    let 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++) {
      let xElements = document.elementsFromPoint(
        (window.innerWidth * i) / 10,
        window.innerHeight / 2
      );
      let yElements = document.elementsFromPoint(
        window.innerWidth / 2,
        (window.innerHeight * i) / 10
      );
      if (isContainer(xElements[0])) emptyPoints++;
      // 中心点只计算一次
      if (i != 5) {
        if (isContainer(yElements[0])) emptyPoints++;
      }
    }
    // 页面正常渲染,停止轮训
    if (emptyPoints != 17) {
      if (skeletonProject) {
        // 第一次不比较
        if (!_whiteLoopNum) return openWhiteLoop();
        // 比较前后dom是否一致
        if (_skeletonNowList.join() == _skeletonInitList.join())
          return callback({
            status: 'error'
          });
      }
      if (window._loopTimer) {
        clearTimeout(window._loopTimer);
        window._loopTimer = null;
      }
    } else {
      // 开启轮训
      if (!window._loopTimer) {
        openWhiteLoop();
      }
    }
    // 17个点都是容器节点算作白屏
    callback({
      status: emptyPoints == 17 ? 'error' : 'ok',
    });
  }
  // 开启白屏轮训
  function openWhiteLoop() {
    if (window._loopTimer) return;
    window._loopTimer = setInterval(() => {
      if (skeletonProject) {
        _whiteLoopNum++;
        _skeletonNowList = [];
      }
      sampling();
    }, 1000);
  }
}

如果不通过外部传参,SDK 能否自己判断是否有骨架屏呢? 比如在页面初始的时候,根据根节点上有没有子节点来判断
因为这套检测方案需要兼容 SSR 服务端渲染的项目,对于 SSR 项目来说,浏览器获取 html 文件的根节点上已经有了 dom 元素,所以最终采用外部传参的方式来区分

总结

这套白屏检测方案是从现象推导本质,可以覆盖绝大多数 SPA 项目的应用场景
小伙们若有其他检测方案,欢迎多多讨论与交流 💕

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/369320.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

线性数据结构:链表 LinkList

一、前言 链表的历史 于1955-1956年&#xff0c;由兰德公司的Allen Newell、Cliff Shaw和Herbert A. Simon开发了链表&#xff0c;作为他们的信息处理语言的主要数据结构。链表的另一个早期出现是由 Hans Peter Luhn 在 1953 年 1 月编写的IBM内部备忘录建议在链式哈希表中使…

推荐使用什么样的平台表单制作工具好?

在办公自动化迅猛发展的今天&#xff0c;传统的表单制作工具已经不能满足各行各业的生产需求&#xff0c;引用专业的低代码开发平台表单制作工具可以助力企业提高作业协作效率。那么&#xff0c;什么平台的表单制作工具可以实现这一目的&#xff1f;今天&#xff0c;我们就一起…

月薪过3W的软件测试工程师,都是怎么做到的?

对任何职业而言&#xff0c;薪资始终都会是众多追求的重要部分。前几年的软件测试行业还是一个风口&#xff0c;随着不断地转行人员以及毕业的大学生疯狂地涌入软件测试行业&#xff0c;目前软件测试行业“缺口”已经基本饱和。当然&#xff0c;我说的是最基础的功能测试的岗位…

用规则来搭建团队:写周报不一定是坏事

你好&#xff0c;我是Smile&#xff0c;一位有二十年工作经验的技术专家。今天我会结合我的经历&#xff0c;和你聊聊搭建技术团队这个话题。 众所周知&#xff0c;技术团队很大程度上决定了一个公司业务的生命力和生命周期&#xff0c;因此技术团队的投入成本往往很高&#x…

快到金3银4了,准备跳槽的可以看看

前两天跟朋友感慨&#xff0c;今年的铜九铁十、裁员、疫情导致好多人都没拿到offer!现在已经12月了&#xff0c;具体明年的金三银四只剩下两个月。 对于想跳槽的职场人来说&#xff0c;绝对要从现在开始做准备了。这时候&#xff0c;很多高薪技术岗、管理岗的缺口和市场需求也…

百度Q4:亮剑AI,重塑金身

2月22日港股盘后&#xff0c;港交所最纯正的AI概念股百度发布了2022年第四季度以及全年的业绩报告&#xff0c;在新冠疫情冲击宏观经济的第四季度&#xff0c;百度经营利润、经营利润率也均实现同比增长。 凭借在AI领域的长期投入&#xff0c;尽管有疫情侵扰、外部环境所带来的…

HACKTHEBOX——Curling

nmap还是老规矩&#xff0c;先扫描目标对外开放端口情况&#xff0c;只发现了22和80端口对外开启nmap -sV -sC -oA nmap 10.10.10.150http80端口对外开启&#xff0c;从扫描结果来看好像运行着Joomla&#xff0c;所以先访问看看&#xff0c;可以看到帖子由super user撰写&#…

教你用反射机制如何几分钟搭建完后端

如果想快速搭建后台跨域使用这些技术 反射mybatis-plusjson 反射可以实现动态数据的传输 一般对数据库进行操作肯定离不开这些代码 如果我们用反射机制只需要这一个就行 而说到反射的好处&#xff0c;一般情况下我们做增删改查需要大量的接口才能完成&#xff0c;而用反射我…

2023如果纯做业务测试的话,在测试行业有出路吗?

直接抛出我的结论&#xff1a;手工做业务类测试&#xff0c;没有前途。 个人建议赶紧从业务测试跳出来&#xff0c;立即学习代码&#xff0c;走自动化测试方向。目前趋势&#xff0c;业务测试需要用自动化做。 为了让大家能够信服我的观点&#xff0c;本文将从以下方面进行阐…

LeetCode题目笔记——2357. 使数组中所有元素都等于零

文章目录题目描述题目链接题目难度——简单方法一&#xff1a;直接模拟代码/Python方法二&#xff1a;哈希表代码/Python总结题目描述 给你一个非负整数数组 nums 。在一步操作中&#xff0c;你必须&#xff1a; 选出一个正整数 x &#xff0c;x 需要小于或等于 nums 中 最小…

嵌入式系统硬件设计与实践(学习方法)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 刚读书的时候&#xff0c;对什么是嵌入式&#xff0c;其实并不太清楚。等到自己知道的时候&#xff0c;已经毕业很多年了。另外对于计算机毕业的学…

RK3588关键电路 PCB Layout设计指南

1、音频接口电路 PCB 设计&#xff08;1&#xff09;所有 CLK 信号建议串接 22ohm 电阻&#xff0c;并靠近 RK3588 放置&#xff0c;提高信号质量&#xff1b;&#xff08;2&#xff09;所有 CLK 信号走线不得挨在一起&#xff0c;避免串扰&#xff1b;需要独立包地&#xff0c…

jianzhiOffer第二版难重点记录

04. 二维数组中的查找https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/ 思路&#xff1a;可以每层用以恶搞二分查找&#xff0c;优化思路&#xff1a;从左下角出发直接用二分。 ​​​​​​07. 重建二叉树https://leetcode.cn/problems/zhong-jian-er-cha…

Redis 常用数据类型之 zset

目录 一、zset数据结构 二、Redis的zset 三、详细操作 基础操作&#xff08;zadd、zcrad、zcount&#xff09; 排序操作&#xff08;zrange 、zrevrange &#xff09; 根据分数显示元素&#xff08;zrangebyscore&#xff09; 删除操作&#xff08;zrem、zremrangebyran…

DSPE-PEG-TCO;磷脂-聚乙二醇-反式环辛烯科研用化学试剂简介

中文名称 磷脂-聚乙二醇-反式环辛烯 英文名称 DSPE-PEG-TCO 外观&#xff1a;粉末或半固体&#xff0c;取决于分子量。 溶剂&#xff1a;溶于大部分有机溶剂&#xff0c;如&#xff1a;DCM、DMF、DMSO、THF等等。在水中有很好的溶解性 稳定性&#xff1a;冷藏保存&#xff…

安装包UI美化之路-通过nsNiuniuSkin来做Electron程序的打包、发布与升级

nsNiuniuSkin从发布之初&#xff0c;因其简单、简洁、高效&#xff0c;受到了非常多公司的青睐&#xff0c;现在已经越来越多的公司采用我们的这套解决方案来制作安装包了&#xff01; 从一个安装包UI插件&#xff0c;逐步演化成一套集美观、安全、简洁、自动化为一体的完整的…

ModBus RTU与ModBus TCP通信协议详解

1、Modbus简介 Modbus通信协议由Modicon公司&#xff08;现已经为施耐德公司并购&#xff0c;成为其旗下的子品牌&#xff09;于1979年发明的&#xff0c;是全球最早用于工业现场的总线规约。由于其免费公开发行&#xff0c;该协议免费使用&#xff0c;Modbus通信协议采用的是主…

电力电子技术复习笔记

绪论电力电子器件直流-直流直流直流动态模型整流电路逆变电路&#xff1a;有源逆变软开关电路期末绪论 电力电子技术&#xff1a;使用电力电子器件对电能进行转换和控制的技术 电力电子技术和信息电子技术的本质区别&#xff1a; 电力电子技术主要用于电能变换 信息电子技术…

2023什么蓝牙耳机好用不贵?适合学生党的国产蓝牙耳机推荐

蓝牙耳机因为摆脱了线的束缚&#xff0c;在日常生活中解放了双手&#xff0c;使用更便捷。现如今的蓝牙耳机越来越多&#xff0c;每款耳机都有自己的侧重。下面&#xff0c;我来给大家推荐几款好用不贵发国产蓝牙耳机&#xff0c;一起来看看吧。 一、南卡小音舱蓝牙耳机 参考…

Linux操作系统基础知识命令参数详解

Linux操作系统 RAID分组 RAID JBOD RAID JBOD的意思是Just a Bunch Of Disks&#xff0c;是将多块硬盘串联起来组成一个大的存储设备&#xff0c;从某种意义上说这种类型不被算作RAID&#xff0c;在维基百科里JBOD同时也被归入非RAID架构。RAID JBOD将所有的磁盘串联成一个单…