最近在开发数据监控大屏时遇到一个棘手的问题:当使用screenfull.js将某个图表区域局部全屏后,ElementUI的Notification组件弹出的告警消息竟然"消失"了。这就像你在电影院看IMAX电影时,服务员递来的爆米花突然穿过屏幕掉进了电影里——明明该出现在眼前的提示,却怎么都找不到。
具体场景是这样的:我们有一个实时监控系统,当服务器推送异常告警时,前端会通过this.$notify()弹出警告。在普通状态下一切正常,但只要把某个图表区域全屏展示,Notification就会神秘失踪。即使把z-index调到9999也无济于事,就像这样:
javascript复制// 全屏操作
handleScreen() {
if (!screenfull.isEnabled) return
screenfull.toggle(this.$refs.fullscreen) // 针对特定DOM元素全屏
}
// 弹出通知
this.$notify({
title: 'CPU过载警告',
message: '服务器A的CPU使用率已达95%',
type: 'warning',
duration: 0,
offset: 100
})
这个问题在需要长时间全屏展示又必须及时响应告警的场景下尤为致命。想象一下运维人员在全屏查看监控曲线时,完全错过了服务器宕机的预警通知,这简直是灾难性的体验。
为什么全屏元素会"吞噬"弹窗?这要从CSS的层叠上下文(Stacking Context)说起。可以把层叠上下文理解为HTML中的"平行宇宙",每个宇宙有自己独立的z轴空间。当元素满足以下条件时会创建新的层叠上下文:
关键问题在于:ElementUI的Notification默认挂载在body下,而局部全屏的元素会创建一个新的层叠上下文,这个上下文就像玻璃天花板,把body中的弹窗"压"在了下面。即使你把弹窗的z-index调到再高,也突破不了这层"结界"。
通过Chrome的Layers面板可以清晰看到,全屏元素创建了新的渲染层(如下图示意)。这就像你在二楼举着气球(弹窗),但一楼天花板(全屏元素)突然升高到三楼,你的气球永远无法突破天花板。
code复制body
├── 全屏元素(创建新层叠上下文)
│ └── 图表等内容
└── Notification(z-index再高也无效)
最初我尝试了几种常见解决方案,但都有明显缺陷:
将Notification挂载到全屏容器内部:
javascript复制this.$notify({
target: this.$refs.fullscreen,
// 其他参数...
})
问题:需要修改每个通知调用点,且当全屏容器切换时会丢失挂载点。
监听全屏事件动态调整样式:
css复制.el-notification {
z-index: 9999 !important;
}
问题:如前所述,层叠上下文隔离使这个方案完全无效。
改用ElementUI的Message组件:
javascript复制this.$message.warning('警告信息')
问题:Message的样式和交互不符合告警提示的需求,且同样存在显示问题。
经过多次实验,我找到了一种更优雅的解决方案——不跟全屏API硬碰硬,而是通过CSS"伪造"全屏效果。具体思路是:
实现代码如下:
html复制<!-- 模板部分 -->
<div ref="fullscreen" :class="{ 'fullscreen-mock': isFull }">
<!-- 你的图表等内容 -->
</div>
javascript复制// 脚本部分
handleScreen() {
if (!screenfull.isEnabled) return
const bodyNode = document.querySelector('body')
screenfull.toggle(bodyNode) // 始终全屏body
}
css复制/* 关键CSS */
.fullscreen-mock {
position: fixed;
z-index: 200; /* 必须小于Notification的2000 */
top: 0;
left: 0;
right: 0;
bottom: 0;
background: white; /* 模拟全屏背景 */
overflow: auto;
}
这个方案的巧妙之处在于:
对于更复杂的场景,我们可以扩展这个方案:
javascript复制data() {
return {
activeFullscreen: null
}
},
methods: {
setFullscreen(refName) {
this.activeFullscreen = refName
screenfull.toggle(document.body)
}
}
css复制/* 动态全屏样式 */
[data-fullscreen="chart1"] {
/* 特定图表1的全屏样式 */
}
[data-fullscreen="chart2"] {
/* 特定图表2的全屏样式 */
}
结合Vuex管理全屏状态:
javascript复制// store/modules/screen.js
export default {
state: {
fullscreenTarget: null
},
mutations: {
setFullscreen(state, payload) {
state.fullscreenTarget = payload
}
}
}
使用CSS变量动态控制层级:
css复制:root {
--fullscreen-z-index: 200;
}
.el-notification {
z-index: var(--notification-z-index, 2000);
}
在实现过程中有几个关键点需要注意:
z-index的取值策略:全屏模拟元素的z-index必须小于Notification的默认值2000,但又要大于页面其他内容。我建议设置在200-500之间。
移动端适配问题:iOS下全屏API有特殊限制,需要添加meta标签:
html复制<meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
css复制.fullscreen-mock {
/* 防止边缘出现滚动条 */
overflow: hidden;
/* 移动端适配 */
height: 100vh;
/* 防止transform影响子元素 */
transform: none;
}
css复制.fullscreen-mock {
will-change: transform;
}
下表对比了各种解决方案的优劣:
| 方案 | 侵入性 | 兼容性 | 维护成本 | 适用场景 |
|---|---|---|---|---|
| 修改挂载节点 | 高 | 好 | 高 | 简单页面 |
| z-index提升 | 低 | 差 | 低 | 不推荐 |
| 本文方案 | 中 | 优秀 | 中 | 复杂系统 |
| iframe方案 | 高 | 优秀 | 高 | 隔离需求 |
根据我的经验,对于大多数后台管理系统和数据大屏,本文的CSS模拟方案是最佳选择。它在不修改ElementUI源码的情况下,用最小成本解决了问题。