彻底掌握页面白屏检测
在日常的前端开发中,我们可能会遇到一些页面白屏的问题。这些问题可能会给用户带来不好的体验,甚至导致用户流失。本文带你彻底了解前端白屏,从此不再畏惧。
前言
在日常的前端开发中,白屏几乎是每个前端开发者都会遇到的问题。白屏问题严重影响了用户体验。当用户访问一个页面时,如果页面长时间处于白屏状态,用户可能会认为页面出现了问题,从而选择离开。这对于任何一个网站都是不利的,尤其是对于那些依赖用户流量的电商网站来说,白屏问题可能直接导致用户流失和收入下降。
许多研究都表明,用户最满意的打开网页时间,是在2秒以下。 用户能够忍受的最长等待时间的中位数,在6~8秒之间。 这就是说,8秒是一个临界值,如果你的网站打开速度在8秒以上,那么很可能,大部分访问者最终都会离你而去。
白屏的主要原因
白屏的主要原因可以从浏览器渲染的整个流程来分析,我们知道从一次请求到页面渲染大致会经过以下过程
用户输入URL -> DNS解析 -> 建立连接 -> 返回内容 -> 浏览器解析响应内容 -> 浏览器渲染
其中任何一环出现问题都有可能造成页面白屏,下面是一些最常见的白屏原因:
1. 资源加载失败页面依赖的关键资源(CSS、JS、图片等)加载失败,导致页面无法正常渲染。
//例如我们在React项目中引入了不存在的js资源或者资源加载失败,就会造成页面无法正常渲染,导致白屏。
import './index.js'
function App() {
return (
<div className="App">
app
</div>
)
}
export default App
这种情况下我们可以在关键的接口和请求上增加响应监控。
2. 资源加载延迟资源加载延迟(或阻塞),导致页面长时间等待资源加载完成。出现空白。
//例如我们在这里,App 组件在挂载后会使用 setTimeout 模拟资源加载延迟 8 秒。
import { useState, useEffect } from 'react'
import Preview from 'preview'
function App() {
const [data, setData] = useState(null)
useEffect(() => {
setTimeout(() => {
setData({ name: 'test' })
}, 10000) // 模拟资源加载 10000 秒
}, [])
return (
<div className="App">
{data && <Preview />}
</div>
)
}
export default App
这种情况可以前端做一些loading
状态或者增加骨架屏,防止页面无内容造成用户困扰。
3. 代码执行中出现未被捕捉的错误,例如JavaScript执行错误,Promise错误等等。导致页面功能无法正常工作,出现空白。
这个错误也是前端最最最常见的一个错误,如果没有处理好异常情况导致抛出错误有没有被捕获,就会造成页面崩溃。
function App() {
return (
<div className="App">
{
JSON.parse(data.name) // 未对data.name判空导致parse解析失败
}
</div>
)
}
export default App
4. 浏览器兼容问题
不同的浏览器对于前端技术的支持程度不同,如果我们使用了浏览器不支持的语法或者CSS类型,可能导致某些浏览器无法正常显示页面。
function App() {
return (
<div className="App">
{data?.name} // 例如浏览器不支持可选链式符就会报错, https://caniuse.com/?search=%3F.
</div>
)
}
export default App
除此之外,第三方服务或资源出现问题,浏览器缓存、CDN问题等也会导致页面白屏的问题。在实际开发中我们需要考虑到各种可能的情况逐步排查。
如何监测页面白屏
要检测白屏,主线的思路就是检测在特定时间特定的DOM是否存在于页面中。或者是通过页面截图并且识别图片是否包含内容等方式(代价太高)。下面就给大家分享几种常用的方式,最后也会给出一种最通用的方式。
1. 全局错误监听 + 判断对应节点是否存在
例如在React
中我们可以使用ErrorBoundary
实现由于代码执行错误导致白屏的检测步骤:
- 首先,创建一个名为
ErrorBoundary
的新组件。在这个组件中,我们需要定义一个名为componentDidCatch
的生命周期方法,它将在子组件中捕获到错误时被调用。同时,我们需要在组件的状态中存储一个表示是否发生错误的变量。
import React, { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
componentDidCatch(error, info) {
// 在这里可以将错误记录到日志,或者发送给后端
console.log(error, info);
this.setState({ hasError: true });
if (!document.getElementById('container').innerHTML) {
console.log('页面白屏了');
}
}
render() {
// if (this.state.hasError) {
// // 如果发生错误,显示备用 UI
// return <h1>出错了,请刷新页面或联系客服。</h1>;
// }
// 如果没有发生错误,正常渲染子组件
return this.props.children;
}
}
export default ErrorBoundary;
- 接下来,将
ErrorBoundary
组件包裹在应用程序的根组件中,以便捕获整个应用程序的错误。
const App = () => {
return <div>{a?.sjakd}</div>
}
import React from 'react';
import ReactDOM from 'react-dom';
import ErrorBoundary from './ErrorBoundary';
ReactDOM.render(
<React.StrictMode>
<ErrorBoundary>
<App />
</ErrorBoundary>
</React.StrictMode>,
document.getElementById('root')
);
现在,如果应用程序中的任何子组件发生错误,ErrorBoundary
组件将捕获该错误,并判断对应的根节点是否函数对应的节点来实现白屏检测。
2. Mutation Observer 监听 DOM 变化
MutationObserver
API 可以用于监测DOM树的变化。我们可以通过MutationObserver
来监测页面的DOM变化,从而判断页面是否出现白屏现象。
class App extends React.Component {
componentDidMount() {
const observer = new MutationObserver((mutations, observer) => {
const duration = Date.now() - observer.startTime;
if (duration > 3000) {
console.log('页面白屏时间超过3秒');
}
});
observer.startTime = Date.now();
observer.observe(document.body, { childList: true, subtree: true });
}
// ...
}
以React框架为例,我们可以在componentDidMount
生命周期函数中使用MutationObserver
API 获取相关信息。如果两次渲染时间过长说明出现了白屏的情况。
但是这种方法对下面这两种情况没有很好的解决办法
1)DOM根本就没有渲染
2)遇到有骨架屏的项目,若页面从始至终就没变化,一直显示骨架屏,这种情况 Mutation Observer 也束手无策
3. 关键点采样对比
所谓关键点采样就是在我们的屏幕中,随机取几个固定的点,利用document.elementsFromPoint(x,y)该函数返还在特定坐标点下的 HTML 元素数组。这也是准确率比较高的一种做法,目前主流的都是这种检测方法。具体实现如下:
1、页面中间取n个采样点,利用 elementsFromPoint api 获取该坐标点下的 HTML 元素。采样方法有垂直选取,交叉选取,以及垂直交叉选取三种方法,对应的采样图片如下:
2、定义属于容器元素的集合,如 ['html', 'body', '#app', '#root']
3、判断这n个采样点是否在该容器集合中。目的就是为了判断采样点有没有内容;如果没有内容,那么我们获取到的 dom 元素就是容器元素,若n个采样点都没有内容则可判定为白屏
4、若初次判断是白屏,开启轮询检测,来确保白屏检测结果的正确性,直到页面的正常渲染或者重试一定次数就关闭定时器
具体代码实现如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<script>
function whiteScreenMonitor(wrapperSelectors) {
// 记录空白点数
let emptyPoints = 0;
let timer = null;
const maxRetryTime = 5;
let retryTime = 0;
// 获取元素的选择器
function getSelector(element) {
if (element?.id) {
return `#${element.id}`;
} if (element?.className) {
return (
`.${
element.className
.split(' ')
.filter(item => !!item)
.join('.')}`
);
}
return element?.nodeName.toLowerCase();
}
// 判断元素是否为包裹元素
function isWrapper(element) {
const selector = getSelector(element);
if (wrapperSelectors.indexOf(selector) !== -1) {
return true;
}
}
function checkBlankScreen() {
// 检查屏幕的点,分别为横向和纵向的点
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,
);
// 取第一个来判断
emptyPoints += !!isWrapper(xElements[0]);
emptyPoints += !!isWrapper(yElements[0]);
}
// 如果空白点数超过16个,表示屏幕为空白
if (emptyPoints > 16) {
console.log(`这是第 ${retryTime} 次检测,页面白屏了`);
if (++retryTime > maxRetryTime) {
console.log('页面白屏检测超过最大次数,可判定为白屏');
// 这里可以做一些监控上报之类的事情
clearTimeout(timer);
return;
}
if (!timer) {
timer = setInterval(() => {
emptyPoints = 0;
checkBlankScreen();
}, 1000);
}
} else {
clearTimeout(timer);
}
}
window.addEventListener('load', checkBlankScreen);
}
// 开始检测白屏,如果是SDK则可以将这个当法导出
whiteScreenMonitor(['html', 'body', '#container', '.content', '#app', '#root']);
</script>
<body>
<div id="root"></div>
</body>
<script>
setTimeout(() => {
const content = document.createElement('div')
content.style.width = '500px'
content.style.height = '500px'
content.style.backgroundColor = 'red'
document.getElementById('root').appendChild(content) // 挂载
}, 10000); // 模拟白屏的操作
</script>
</html>
这里使用初次检测 + 轮训防止误判的方式来进行白屏检测。关键内容在函数checkBlankScreen
中取点并判断内容是容器节点的个数。
使用了骨架屏的页面如何检测白屏
对于有骨架屏的页面,用户打开页面后,先看到骨架屏,然后再显示正常的页面,来提升用户体验;但如果页面从始至终都显示骨架屏,也算是白屏的一种
骨架屏示例:https://ant-design.antgroup.com/components/skeleton-cn
检测骨架屏的白屏的方式其实也很简单,我们可以稍微改改上面的方法即可:
- 传入骨架屏对应的容器给
whiteScreenMonitor
函数,这样检测的时候就可以将骨架屏的内容过滤掉。 - 改变检测白屏的方式,我们通过每次获取到的内容和第一次获取到的内容进行对比,如果每一次都相同说明是白屏的。但是这里就需要保证我们的白屏检测代码一定要是最先运行的,不然等我们所有的内容都渲染完了再检测,这是后内容也是没有改变的。
总结
以上就是常用的白屏检测方法了,相信看完大家以后对于如何检测项目中白屏有了自己的看法。
最后打个广告,我新开了个公众号,旨在将自己日常学习的内容进行沉淀。这个公众号会经常更新前端相关的技术文章,还请大家多多支持,点点关注💗。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)