前端白屏监控原理
前言
前端基建里最重要的事情之一就是监控,性能,报错,白屏等等,而今天要说的就是白屏的监控。
前端白屏是影响用户体验的常见问题,通常有资源加载失败、JS 执行错误、渲染阻塞、框架异常等原因。
今天就以页面生命周期、错误捕获、性能指标、框架特性等维度来描述怎么监控。
关键节点判断
核心原理
不管是传统框架、界面、还是现代浏览器框架,都会有一个容器节点、关键节点,例如根节点,header节点,logo 节点等等,我们要做的就是在页面加载完成之后判断它是否存在即可
关键检测维度
- 元素是否存在:
document.querySelector(selector)是否返回非 null 值(排除因 HTML 结构错误导致的元素缺失)。 - 是否有实际内容:元素的
textContent.trim()不为空(排除空标签),或childNodes.length > 0(存在子元素)。 - 是否可见:
- 布局可见性:
offsetHeight > 0且offsetWidth > 0(排除display: none或内容被完全遮挡)。 - 样式可见性:
getComputedStyle(element).visibility !== 'hidden'且opacity > 0(排除透明或隐藏样式)。
- 布局可见性:
function checkCriticalElement(selector, options = {}) {
const {
timeout = 5000, // 超时阈值(默认5秒)
interval = 500, // 检测间隔(默认500ms,平衡精度与性能)
onWhiteScreen = () => {} // 白屏回调
} = options;
const startTime = Date.now();
const timer = setInterval(() => {
const now = Date.now();
// 1. 超时判断:超过阈值仍未检测到有效元素,触发白屏
if (now - startTime > timeout) {
clearInterval(timer);
onWhiteScreen({
type: 'critical_element_timeout',
selector,
duration: now - startTime,
reason: '元素未在规定时间内加载完成'
});
return;
}
// 2. 元素存在性检测
const element = document.querySelector(selector);
if (!element) return; // 元素未加载,继续等待
// 3. 内容有效性检测
const hasContent = element.textContent.trim() !== '' || element.childNodes.length > 0;
if (!hasContent) return; // 元素存在但无内容,继续等待
// 4. 可见性检测
const computedStyle = getComputedStyle(element);
const isVisible =
element.offsetHeight > 0 &&
element.offsetWidth > 0 &&
computedStyle.visibility !== 'hidden' &&
computedStyle.opacity > 0;
if (isVisible) {
clearInterval(timer); // 所有条件满足,停止检测
}
}, interval);
}
触发时机
- 首屏加载:在
DOMContentLoaded事件后启动检测 - 单页应用(SPA)路由切换:在路由钩子(如 Vue 的
router.afterEach、React 的useEffect监听路由变化)中触发,检测新页面的关键元素。 - 动态内容加载:对于异步渲染的内容(如列表、表单),在接口请求完成后启动检测。
错误捕获
1. JS 运行时错误捕获
同步错误(window.onerror)
- 触发场景:直接执行的 JS 代码抛出未捕获的错误(如
undefined.xxx、语法错误)。 - 参数详解:
message:错误信息(字符串)。source:错误发生的脚本 URL。lineno/colno:错误行号 / 列号。error:错误对象(含stack调用栈,最关键的排查依据)。
window.onerror = function(message, source, lineno, colno, error) {
// 过滤非关键错误(如第三方脚本的非阻塞错误)
const isCritical = source.includes('/app.') || source.includes('/main.'); // 仅关注核心脚本
if (isCritical) {
reportError({
type: 'js_runtime_error',
message: error?.message || message,
stack: error?.stack || `at ${source}:${lineno}:${colno}`,
time: Date.now()
});
}
return true;
};
异步错误(window.onunhandledrejection)
- 触发场景:Promise 链式调用中未通过
.catch()处理的错误(如接口请求失败、async/await未用try/catch)。
window.onunhandledrejection = function(event) {
const reason = event.reason;
reportError({
type: 'unhandled_promise',
message: reason?.message || String(reason),
stack: reason?.stack,
time: Date.now()
});
event.preventDefault(); // 阻止浏览器默认警告
};
2. 资源加载错误捕获
触发场景
- 脚本(
<script>)加载失败(404/500 状态、跨域限制)。 - 样式表(
<link rel="stylesheet">)加载失败(导致页面无样式,视觉上白屏)。
window.addEventListener('error', (event) => {
const target = event.target;
// 仅处理资源加载错误
if (!['SCRIPT', 'LINK', 'IMG'].includes(target.tagName)) return;
// 判断是否为关键资源(根据业务定义)
const isCritical =
(target.tagName === 'SCRIPT' && target.src.includes('/vue.runtime') || target.src.includes('/app.')) ||
(target.tagName === 'LINK' && target.rel === 'stylesheet' && target.href.includes('/main.css'));
if (isCritical) {
reportError({
type: 'resource_load_error',
tag: target.tagName,
url: target.src || target.href,
status: target.error?.status || 'unknown', // 部分浏览器返回HTTP状态码
time: Date.now()
});
}
}, true); // 捕获阶段监听
小结
快速定位因代码错误或资源缺失导致的白屏(如框架脚本加载失败直接导致无法渲染)。但是并非所有错误都会导致白屏(如非首屏脚本错误),需通过 “关键资源 / 脚本” 过滤。
基于性能指标的检测
核心原理
Web 性能 API 提供了页面加载和渲染的关键时间节点,通过监控这些指标可判断渲染是否正常:
- 若 “首屏绘制(FCP)” 未发生或超时,说明页面未开始渲染;
- 若 “最大内容绘制(LCP)” 超时,说明核心内容未加载完成,可能处于白屏或半成品状态。
1. 首屏绘制(FCP)监控
定义
FCP(First Contentful Paint)指浏览器首次绘制文本、图片、非白色背景的 SVG 或 Canvas 元素的时间,是页面 “从白屏到有内容” 的第一个关键节点。
检测逻辑
- 通过
PerformanceObserver监听first-contentful-paint类型的性能条目。 - 若 FCP 时间超过业务阈值(如 8 秒),或未检测到 FCP 条目(说明未开始渲染),则判定为白屏风险。
// 监听FCP指标
const fcpObserver = new PerformanceObserver((entriesList) => {
const entries = entriesList.getEntries();
if (entries.length === 0) return;
const fcpEntry = entries[0];
const fcpTime = fcpEntry.startTime; // 相对于页面导航开始的时间(ms)
const navigationStart = performance.timing.navigationStart;
const absoluteTime = new Date(navigationStart + fcpTime).toISOString(); // 绝对时间
// 阈值判断(根据业务场景调整,如低端设备可放宽至10秒)
if (fcpTime > 8000) {
reportPerformance({
type: 'fcp_timeout',
fcpTime: Math.round(fcpTime),
absoluteTime,
message: `首屏绘制超时(阈值8秒)`
});
}
});
// 启动监听(buffered: true 表示监听已发生的指标)
fcpObserver.observe({ type: 'first-contentful-paint', buffered: true });
// 兜底:若页面加载完成后仍未检测到FCP,判定为白屏
window.addEventListener('load', () => {
const fcpEntries = performance.getEntriesByType('first-contentful-paint');
if (fcpEntries.length === 0) {
reportPerformance({ type: 'fcp_missing', message: '未检测到首屏绘制' });
}
});
2. 最大内容绘制(LCP)监控
定义
LCP(Largest Contentful Paint)指页面加载过程中,最大的内容元素(文本块或图片)完成绘制的时间,反映核心内容的加载进度。
检测逻辑
- LCP 通常在 FCP 之后发生,若 LCP 超时(如 12 秒),说明核心内容未加载,可能处于 “部分白屏” 状态。
- 记录 LCP 对应的元素(
fcpEntry.element),便于分析是文本还是图片未加载。
const lcpObserver = new PerformanceObserver((entriesList) => {
const entries = entriesList.getEntries();
if (entries.length === 0) return;
// LCP可能会多次触发(如图片加载完成后尺寸变化),取最后一次
const lcpEntry = entries[entries.length - 1];
const lcpTime = lcpEntry.startTime;
if (lcpTime > 12000) { // 阈值12秒
reportPerformance({
type: 'lcp_timeout',
lcpTime: Math.round(lcpTime),
element: lcpEntry.element?.outerHTML || 'unknown', // 记录最大内容元素
message: `最大内容绘制超时(阈值12秒)`
});
}
});
lcpObserver.observe({ type: 'largest-contentful-paint', buffered: true });
3. 页面加载阶段耗时分析
通过 performance.timing 分析各阶段耗时,定位阻塞渲染的环节:
domInteractive:DOM 结构解析完成时间(若过长,可能是 HTML 体积过大或解析阻塞)。domContentLoadedEventEnd:DOM 解析 + 初始脚本执行完成时间(若过长,可能是同步脚本执行耗时)。loadEventEnd:所有资源(图片、样式等)加载完成时间(若过长,可能是资源过多或网络慢)。
window.addEventListener('load', () => {
const timing = performance.timing;
const navigationStart = timing.navigationStart;
// 计算各阶段耗时
const domParseTime = timing.domInteractive - navigationStart; // DOM解析耗时
const scriptExecTime = timing.domContentLoadedEventEnd - timing.domInteractive; // 初始脚本执行耗时
const resourceLoadTime = timing.loadEventEnd - timing.domContentLoadedEventEnd; // 资源加载耗时
// 异常判断
if (domParseTime > 3000) { // DOM解析超过3秒
reportPerformance({ type: 'dom_parse_slow', domParseTime });
}
if (scriptExecTime > 5000) { // 脚本执行超过5秒(可能阻塞渲染)
reportPerformance({ type: 'script_exec_slow', scriptExecTime });
}
});
小结
- 适用于检测因 “渲染阻塞”(如慢脚本、大资源)导致的白屏,尤其适合首屏加载场景。
- 性能指标受设备和网络影响极大(如 3G 网络 FCP 阈值应高于 WiFi),需结合用户设备等级动态调整
框架钩子监听
核心原理
单页应用(SPA)的渲染逻辑依赖框架(Vue/React)的组件系统,框架层面的异常(如组件渲染失败、路由跳转错误)是白屏的高频原因。框架提供了专属的错误捕获机制,可精准定位组件级问题。
React 框架异常监听
ErrorBoundary 组件
- 原理:React 16+ 提供的错误边界机制,可捕获子组件树中的渲染错误、生命周期错误、构造函数错误,并返回降级 UI(避免整个应用崩溃白屏)。
- 限制:无法捕获以下错误:
- 事件处理函数中的错误(需手动
try/catch); - 异步代码中的错误(如
setTimeout、Promise); - 服务器端渲染错误;
- 自身组件的错误(仅捕获子组件)。
- 事件处理函数中的错误(需手动
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
// 静态方法:更新状态以触发降级UI
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
// 实例方法:捕获错误并上报
componentDidCatch(error, errorInfo) {
this.setState({ errorInfo });
reportFrameworkError({
framework: 'react',
type: 'component_error',
message: error.message,
stack: error.stack,
componentStack: errorInfo.componentStack, // React组件调用栈
route: window.location.pathname // 当前路由
});
}
render() {
if (this.state.hasError) {
// 降级UI:避免白屏,提示用户刷新
return (
<div style={{ padding: '20px', textAlign: 'center' }}>
<h2>页面加载出错了</h2>
<button onClick={() => window.location.reload()}>刷新重试</button>
</div>
);
}
return this.props.children;
}
}
// 使用方式:包裹整个应用或关键路由
ReactDOM.render(
<ErrorBoundary>
<BrowserRouter>
<App />
</BrowserRouter>
</ErrorBoundary>,
document.getElementById('root')
);
路由错误监听(React Router)
- 异步路由加载失败(如
React.lazy+Suspense加载组件失败)可通过 ErrorBoundary 捕获,或在loadable等库中监听错误。
小结
适用于SPA 应用中因组件渲染、路由跳转导致的白屏(占 SPA 白屏问题的 60% 以上)
像素检测
核心原理
部分白屏场景无错误日志且关键元素存在(如 CSS 样式错乱导致内容被隐藏、背景色与内容色一致),此时需从视觉像素层面判断是否有有效内容。
实现方案(两种思路)
1. 简化版:基于元素尺寸与内容密度
通过检测页面核心区域的尺寸和内容复杂度判断,避免高性能消耗的像素分析:
- 核心区域(如
#app)的scrollHeight是否大于视口高度(排除完全空白)。 - 内容密度:文本长度 + 图片数量是否达到阈值(如文本 > 100 字符或图片 > 1 张)。
function checkVisualContentDensity() {
const app = document.querySelector('#app');
if (!app) return false;
// 1. 尺寸检测:核心区域高度是否足够(至少为视口的80%)
const viewportHeight = window.innerHeight;
const appHeight = app.scrollHeight;
if (appHeight < viewportHeight * 0.8) return false;
// 2. 内容密度检测:文本长度 + 图片数量
const textLength = app.textContent.trim().length;
const imageCount = app.querySelectorAll('img[src]').length;
const hasEnoughContent = textLength > 100 || imageCount > 0;
return hasEnoughContent;
}
// 定时检测(如路由切换后3秒)
setTimeout(() => {
if (!checkVisualContentDensity()) {
reportVisualError({
type: 'low_content_density',
message: '页面内容密度过低,可能存在视觉白屏'
});
}
}, 3000);
2. 进阶版:基于 Canvas 像素分析
通过 html2canvas 库将页面关键区域转为 Canvas,分析像素颜色分布:
- 若超过 90% 的像素为同一颜色(如白色
#ffffff),判定为白屏。
import html2canvas from 'html2canvas';
async function checkVisualPixels() {
const app = document.querySelector('#app');
if (!app) return;
try {
// 将#app区域转为Canvas
const canvas = await html2canvas(app, {
useCORS: true, // 允许跨域图片
logging: false
});
const ctx = canvas.getContext('2d');
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const pixels = imageData.data; // 像素数据(RGBA数组)
// 统计白色像素占比(RGB均为255,透明度255)
let whitePixelCount = 0;
const totalPixels = pixels.length / 4; // 每个像素4个值(RGBA)
for (let i = 0; i < pixels.length; i += 4) {
const r = pixels[i];
const g = pixels[i + 1];
const b = pixels[i + 2];
const a = pixels[i + 3];
if (r === 255 && g === 255 && b === 255 && a === 255) {
whitePixelCount++;
}
}
const whiteRatio = whitePixelCount / totalPixels;
if (whiteRatio > 0.9) { // 白色像素占比超90%
reportVisualError({
type: 'high_white_ratio',
ratio: whiteRatio.toFixed(2),
message: `页面白色像素占比过高(${whiteRatio*100}%)`
});
}
} catch (err) {
console.error('像素分析失败', err);
}
}
// 谨慎使用:性能消耗较高,建议仅在关键场景触发(如其他检测疑似白屏时)
checkVisualPixels();
小结
- 性能消耗大(Canvas 绘制和像素分析耗时),不宜高频执行。
- 受页面设计影响(如本身为极简风格,白色占比高易误报)。
- 本人不太推荐只使用此种方案。
总结
在生产环境中,前端白屏监听的核心目标是:高覆盖率(覆盖绝大多数白屏场景)、低误报(避免无效告警)、低性能损耗(不影响用户体验)、可溯源(能定位根因)。
单一方法难以覆盖所有白屏场景,需结合多种手段形成闭环:
| 方法类型 | 核心手段 | 适用场景 |
|---|---|---|
| 关键元素检测 | 定时检查 DOM 存在性和内容 | 首屏加载、路由切换后白屏 |
| 错误捕获 | JS 错误、资源加载错误 | 代码异常导致的白屏 |
| 性能指标监控 | FCP、LCP、DOM 就绪时间 | 渲染阻塞导致的白屏 |
| 框架异常监听 | Vue errorHandler、React ErrorBoundary | 组件渲染错误导致的白屏 |
| 视觉检测 | 内容高度 / 像素分析 | 样式错乱导致的白屏 |
个人结尾推荐先使用JS 错误捕获 + 资源加载错误捕获+框架错误捕获, 作为最核心的错误监控,其余的检测方式可根据具体场景再行分析。

浙公网安备 33010602011771号