🤖 作者简介:水煮白菜王,一位前端劝退师 👻
👀 文章专栏: 前端专栏 ,记录一下平时在博客写作中,总结出的一些开发技巧和知识归纳总结✍。
感谢支持💕💕💕
目录
- 资源加载问题
- 1.可恢复的白屏
- 2.不可恢复的白屏
- 代码执行错误
- 白屏检测
- 检测根节点是否渲染
- Mutation Observer 监听 DOM 变化
- 页面截图
- 解决办法
白屏通常指的是页面打开后,浏览器上面的地址栏已经显示完整的 URL,但是页面内容无法渲染,只有白色的空白页面。
导致白屏的原因大致可分为两类:
- 资源加载问题
- 代码执行错误
从现代前端视角来看,这两种原因都跟当前SPA框架的广泛使用有关。
资源加载问题
这里的资源特指 JavaScript 脚本、样式表、图片等静态资源,不包括接口调用等动态资源。
资源加载问题导致的白屏可以分为可恢复的和不可恢复的两大类
1.可恢复的白屏
可恢复的白屏常见于第一次进人页面时,由于资源加载过慢或者接口请求未返回,所以浏览器无法执行下一步骤。
这种白屏通常是网络状况太差或者设备性能太差等原因导致的,一般在浏览器返回后,就能恢复页面渲染,可以通过监控首屏时间来发现。
如果生产环境的首屏时间呈异常上升趋势,那么一定是页面白屏时间过长导致的,开发人员应该及时关注并排查近期改动的代码
2.不可恢复的白屏
不可恢复的白屏大多是由于网络或缓存问题导致的。
常见的例子莫过于 React、Vue 等 SPA 框架构建的 Web 应用,一旦 [bundle|app].js 因为网络原因访问失败,便会引发页面白屏。
你可以访问任意SPA框架构建的Web应用,按照如下步骤复现:打开 DevTools > Network,找到 app.xxx.js,右键并选中 Block request URL,随后刷新页面。
另一个例子是SPA项目打包后,在非首次线上替换dist文件时,某些手机/浏览器在之后首次打开页面,可能出现白屏情况
在用户端会默认缓存index.html入口文件,而由于vue打包生成的css/js都是哈希值,跟上次的文件名都不同,因此会出现找不到css/js的情况,导致白屏的产生。在服务端更新包之后,由于旧的文件被删除,而index.html所链接的路径依然是旧文件路径,因此会找不到文件,从而白屏。
代码执行错误
代码执行错误导致的白屏一般伴随着功能流程的阻断出现,并且很难通过等待或者页面刷新等方法修复。
这类问题出现的原因通常是前端代码逻辑错误,或是后端接口的脏数据导致的前端数据解析逻辑错误,最终导致运行异常的前端代码触发了页面崩溃。
例如,如果 React 中的组件发生了异常,并且外部没有使用 componentDidCatch 或者getDerivedStateFromError 捕错误,那么 React 组件 render 挂载的目标节点下的 DOM 树会被移除、页面就会出现白屏。
自React 16 起,任何未被错误边界捕获的错误将会导致整个 React 组件树被卸载。
白屏检测
检测根节点是否渲染
这种方法的原理是在当前主流 SPA 框架下,DOM 一般挂载在一个根节点之下(比如 < div id=“app” > ),发生白屏后通常是根节点下所有 DOM 被卸载。
我们可以通过在页面渲染完成后,检查页面根节点的子元素是否存在,如果不存在,则可以判断页面是白屏状态。
这种方案简洁明了,但缺点也很明显,项目有骨架屏渲染的情况下无法判断是否白屏。
如在 Vue.js 中可以使用钩子函数 mounted 或 created 在页面渲染完成后进行检测。
export default {
created() {
// 获取页面根节点
const rootNode = document.querySelector('#app');
// 检测根节点的子元素是否存在
if (rootNode.children.length === 0) {
// 页面是白屏状态
console.error('页面白屏了!');
} else {
// 页面正常显示
console.log('页面正常显示');
}
},
}
由于 SPA 应用通常使用异步加载方式加载组件和数据,因此需要确保在页面组件渲染完成后再进行白屏检测。如果在组件渲染之前进行检测,可能会误判为白屏。另外,SPA 应用可能会存在某些异步请求失败或加载超时等问题,也需要考虑这些因素对白屏检测的影响。
Mutation Observer 监听 DOM 变化
Mutation Observer 是一种在 DOM 树发生变化时进行回调的方式,可以用于监听页面元素的变化。可以通过 Mutation Observer 来监听 DOM 树变化,从而判断页面是否白屏。
但这个方式的缺点也很明显:
- 对性能的影响:使用 Mutation Observer 监听 DOM 变化会对浏览器性能造成一定影响,尤其是当 DOM
树变化频繁时,会导致回调函数频繁执行,影响页面的响应速度和用户体验。 - 兼容性问题:Mutation Observer 的兼容性不是很好,不同浏览器支持程度不同,需要进行兼容性处理。
- 误判问题:有些情况下,页面并不是真正的白屏,但是 Mutation Observer 仍然会误判为白屏,例如页面中有一个空白的 div元素占位,此时即使页面内容未加载,也不会被判定为白屏状态。
具体实现步骤如下:
- 创建一个 Mutation Observer 实例,并指定回调函数。
const observer = new MutationObserver((mutations) => {
// mutations 表示 DOM 树的变化列表
});
- 将 Mutation Observer 实例与根节点进行绑定,并指定监测的选项。
const rootNode = document.documentElement;
const observerConfig = {
childList: true, // 监听子节点的变化
subtree: true, // 监听所有后代节点的变化
attributes: true, // 监听节点属性的变化
characterData: true, // 监听节点文本内容的变化
attributeOldValue: true, // 记录节点属性变化前的值
characterDataOldValue: true, // 记录节点文本内容变化前的值
};
observer.observe(rootNode, observerConfig);
- 在回调函数中检查 DOM 树的变化,如果发现根节点的子元素存在,则可以判断页面不是白屏状态。
const observer = new MutationObserver((mutations) => {
// 检查根节点的子元素是否存在
if (rootNode.children.length > 0) {
console.log('页面正常显示');
} else {
console.error('页面白屏了!');
}
});
页面截图
通过对网页进行截图,对截图进行像素点分析,判断页面是否白屏。一般情况下,可以统计截图中所有像素点的 RGB 值,如果所有像素点的 RGB 值都相同,且与白色(RGB(255, 255, 255))非常接近,那么就可以判断页面是白屏状态。
这个方式的缺点如下:
- 页面截图需要包含足够的像素点,以便能够准确地检测页面是否白屏。如果截图不够清晰,可能会导致误判。
- 有些情况下,页面并不是真正的白屏,但是由于一些外部原因(例如网络问题)导致页面未加载,此时页面截图也会被判定为白屏状态。
- 页面存在骨架屏的情况下,需要对比骨架屏
解决办法
思路:减小打包后的体积(sourceMap关掉,CDN引入, 路由懒加载,组件按需加载)
- 提高渲染速度
- 优化用户体验
- CDN资源优化
将依赖的第三方npm包全部改为通过CDN链接获取,在index.html里插入相应链接
<body>
<div id="app"></div>
<script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
<script src="https://cdn.bootcss.com/axios/0.19.0-beta.1/axios.min.js"></script>
<script src="https://cdn.bootcss.com/vuex/3.1.0/vuex.min.js"></script>
<script src="https://cdn.bootcss.com/vue-router/3.0.2/vue-router.min.js"></script>
<script src="https://cdn.bootcss.com/element-ui/2.6.1/index.js"></script>
</body>
在vue.config.js里配置externals属性
module.exports = {
···
externals: {
'vue': 'Vue',
'vuex': 'Vuex',
'vue-router': 'VueRouter',
'axios':'axios',
'element-ui': 'ElementUI'
}
}
卸载相关依赖的npm包
npm uninstall xxx
使用gzip压缩
前端处理:
// npm i compression-webpack-plugin -S
const CompressionPlugin = require('compression-webpack-plugin');
module.exports = {
productionSourceMap: false,
configureWebpack: config => {
if (process.env.NODE_ENV === 'production') {
return {
plugins: [
new CompressionPlugin({
// 匹配规格
test: /\.js$|\.html$|\.css$|\.png$/,
// 文件超过多大进行压缩 单位Byte
threshold: 10240,
// 是否删除源文件(建议不删除)
deleteOriginalAssets: false
})
],
}
}
},
}
还需要在 nginx开启gzip压缩, 例如:
gzip on;
gzip_static on; //当存在.gzip格式的js文件时,优先使用静态文件
gzip_min_length 10k; //开启gzip压缩的最小大小
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 6;
gzip_types text/plain application/javascript application/x-javascript text/javascript text/css application/xml;
gzip_vary on;
gzip_proxied expired no-cache no-store private auth;
gzip_disable "MSIE [1-6]\.";
注意:
当nginx开启gzip压缩的时候,无论前端打包出来的文件是否压缩,网站加载到的js文件都是经过nginx实时压缩过的 。
当gzip_static off的时候,前端上传的js压缩文件(gzip格式那些)并没有什么用。
当gzip_static on时,优先加载前端打包的gzip压缩文件,如果没有找到该文件,那么nginx将实时压缩之后传给浏览器。
如果你觉得这篇文章对你有帮助,请点赞 👍、收藏 👏 并关注我!👀