前言
在日常的前端开发中,白屏几乎是每个前端开发者都会遇到的问题。白屏问题严重影响了用户体验。当用户访问一个页面时,如果页面长时间处于白屏状态,用户可能会认为页面出现了问题,从而选择离开。这对于任何一个网站都是不利的,尤其是对于那些依赖用户流量的电商网站来说,白屏问题可能直接导致用户流失和收入下降。
许多研究都表明,用户最满意的打开网页时间,是在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
函数,这样检测的时候就可以将骨架屏的内容过滤掉。 - 改变检测白屏的方式,我们通过每次获取到的内容和第一次获取到的内容进行对比,如果每一次都相同说明是白屏的。但是这里就需要保证我们的白屏检测代码一定要是最先运行的,不然等我们所有的内容都渲染完了再检测,这是后内容也是没有改变的。
总结
以上就是常用的白屏检测方法了,相信看完大家以后对于如何检测项目中白屏有了自己的看法。
最后打个广告,我新开了个公众号,旨在将自己日常学习的内容进行沉淀。这个公众号会经常更新前端相关的技术文章,还请大家多多支持,点点关注💗。