Vue全屏模式下彈窗顯示的解決過程
Vue全屏模式下彈窗顯示解決
問題背景
在 Web 應(yīng)用中,當(dāng)某個(gè)元素進(jìn)入全屏模式時(shí),普通的彈窗、遮罩層或?qū)υ捒蚪M件會(huì)因?yàn)閷盈B上下文(Stacking Context)的限制而無法正常顯示。
這是因?yàn)椋?/p>
- 全屏元素創(chuàng)建新的層疊上下文:全屏元素會(huì)成為一個(gè)獨(dú)立的渲染層
- z-index 失效:非全屏元素的 z-index 無法穿透到全屏層
- Teleport 目標(biāo)錯(cuò)誤:Vue 的 Teleport 組件默認(rèn)掛載到 body,但全屏?xí)r需要掛載到全屏元素內(nèi)部
核心解決方案
1. 技術(shù)原理
通過動(dòng)態(tài)切換 Teleport 目標(biāo)和強(qiáng)制樣式覆蓋來確保彈窗在任何狀態(tài)下都能正常顯示。
2. 實(shí)現(xiàn)步驟
步驟一:全屏狀態(tài)檢測與管理
import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue';
// 全屏狀態(tài)管理
const isFullscreen = ref(false);
const fullscreenElement = ref(null);
const teleportTarget = ref('body');
// 全屏狀態(tài)變化處理
const handleFullscreenChange = () => {
isFullscreen.value = !!document.fullscreenElement;
fullscreenElement.value = document.fullscreenElement;
updateTeleportTarget();
console.log('全屏狀態(tài)變化:', {
isFullscreen: isFullscreen.value,
fullscreenElement: fullscreenElement.value?.tagName,
teleportTarget: teleportTarget.value
});
};
// 生命周期事件監(jiān)聽
onMounted(() => {
// 初始檢測
isFullscreen.value = !!document.fullscreenElement;
fullscreenElement.value = document.fullscreenElement;
updateTeleportTarget();
// 監(jiān)聽各種瀏覽器的全屏事件
document.addEventListener('fullscreenchange', handleFullscreenChange);
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
document.addEventListener('mozfullscreenchange', handleFullscreenChange);
document.addEventListener('MSFullscreenChange', handleFullscreenChange);
});
onUnmounted(() => {
// 清理事件監(jiān)聽器
document.removeEventListener('fullscreenchange', handleFullscreenChange);
document.removeEventListener('webkitfullscreenchange', handleFullscreenChange);
document.removeEventListener('mozfullscreenchange', handleFullscreenChange);
document.removeEventListener('MSFullscreenChange', handleFullscreenChange);
});步驟二:動(dòng)態(tài) Teleport 目標(biāo)切換
// 更新 Teleport 目標(biāo)
const updateTeleportTarget = () => {
if (isFullscreen.value && fullscreenElement.value) {
// 全屏狀態(tài):Teleport 到全屏元素內(nèi)部
if (!fullscreenElement.value.id) {
fullscreenElement.value.id = `fullscreen-container-${Date.now()}`;
}
teleportTarget.value = `#${fullscreenElement.value.id}`;
} else {
// 非全屏狀態(tài):Teleport 到 body
teleportTarget.value = 'body';
}
};
// 監(jiān)聽彈窗顯示狀態(tài)變化
watch(() => props.visible, (newVisible) => {
if (newVisible) {
nextTick(() => {
updateTeleportTarget();
if (isFullscreen.value) {
forceRerender();
}
});
}
}, { immediate: true });步驟三:模板結(jié)構(gòu)
<template>
<Teleport :to="teleportTarget">
<Transition
enter-active-class="transition-all duration-300 ease-out"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="transition-all duration-200 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div
v-if="visible"
class="modal-container"
:class="{ 'modal-fullscreen': isFullscreen }"
role="dialog"
aria-modal="true"
ref="modalRef"
>
<div class="modal-content" @click.stop>
<!-- 彈窗內(nèi)容 -->
</div>
</div>
</Transition>
</Teleport>
</template>步驟四:關(guān)鍵樣式
/* 基礎(chǔ)容器樣式 */
.modal-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(8px);
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
z-index: 9999;
}
/* 全屏狀態(tài)特殊樣式 - 關(guān)鍵! */
.modal-fullscreen {
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
width: 100% !important;
height: 100% !important;
z-index: 2147483647 !important; /* 使用最高層級(jí) */
background: rgba(0, 0, 0, 0.5) !important;
backdrop-filter: blur(8px) !important;
}
/* 強(qiáng)制顯示 - 防止被隱藏 */
.modal-container {
visibility: visible !important;
opacity: 1 !important;
pointer-events: auto !important;
display: flex !important;
}
/* 針對(duì)全屏容器的樣式重寫 */
*:fullscreen .modal-container,
*:-webkit-full-screen .modal-container,
*:-moz-full-screen .modal-container {
position: absolute !important;
z-index: 999999 !important;
background: rgba(0, 0, 0, 0.5) !important;
backdrop-filter: blur(8px) !important;
}
/* 彈窗內(nèi)容 */
.modal-content {
background: white;
border-radius: 1rem;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
width: 100%;
max-width: 80rem;
height: auto;
min-height: 500px;
position: relative;
z-index: 10;
overflow: hidden;
}
/* 全屏模式下的內(nèi)容調(diào)整 */
.modal-fullscreen .modal-content {
max-width: 90vw;
max-height: 90vh;
}
/* 響應(yīng)式處理 */
@media (max-width: 768px) {
.modal-content {
max-width: 95%;
margin: 0.5rem;
}
.modal-fullscreen .modal-content {
max-width: 95vw;
max-height: 85vh;
}
}
/* 確保在任何情況下都不會(huì)被隱藏 */
.modal-container[style*="display: none"] {
display: flex !important;
}
.modal-container[style*="visibility: hidden"] {
visibility: visible !important;
}
.modal-container[style*="opacity: 0"] {
opacity: 1 !important;
}步驟五:備用強(qiáng)制渲染方案
// 強(qiáng)制重新渲染彈窗(備用方案)
const forceRerender = () => {
if (!props.visible || !isFullscreen.value) return;
nextTick(() => {
const modal = modalRef.value;
if (modal && fullscreenElement.value) {
// 確保彈窗在全屏元素中可見
modal.style.position = 'fixed';
modal.style.top = '0';
modal.style.left = '0';
modal.style.width = '100%';
modal.style.height = '100%';
modal.style.zIndex = '2147483647';
}
});
};完整代碼模板
<template>
<Teleport :to="teleportTarget">
<Transition
enter-active-class="transition-all duration-300 ease-out"
enter-from-class="opacity-0"
enter-to-class="opacity-100"
leave-active-class="transition-all duration-200 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<div
v-if="visible"
class="fullscreen-modal-container"
:class="{ 'fullscreen-modal-fullscreen': isFullscreen }"
role="dialog"
aria-modal="true"
ref="modalRef"
>
<div class="fullscreen-modal-content" @click.stop>
<!-- 你的彈窗內(nèi)容 -->
<slot />
</div>
</div>
</Transition>
</Teleport>
</template>
<script setup>
import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue';
const props = defineProps({
visible: {
type: Boolean,
default: false
}
});
const emit = defineEmits(['update:visible']);
// 組件引用
const modalRef = ref(null);
// 全屏狀態(tài)管理
const isFullscreen = ref(false);
const fullscreenElement = ref(null);
const teleportTarget = ref('body');
// 更新Teleport目標(biāo)
const updateTeleportTarget = () => {
if (isFullscreen.value && fullscreenElement.value) {
if (!fullscreenElement.value.id) {
fullscreenElement.value.id = `fullscreen-container-${Date.now()}`;
}
teleportTarget.value = `#${fullscreenElement.value.id}`;
} else {
teleportTarget.value = 'body';
}
};
// 全屏狀態(tài)變化處理
const handleFullscreenChange = () => {
isFullscreen.value = !!document.fullscreenElement;
fullscreenElement.value = document.fullscreenElement;
updateTeleportTarget();
};
// 強(qiáng)制重新渲染
const forceRerender = () => {
if (!props.visible || !isFullscreen.value) return;
nextTick(() => {
const modal = modalRef.value;
if (modal && fullscreenElement.value) {
modal.style.position = 'fixed';
modal.style.top = '0';
modal.style.left = '0';
modal.style.width = '100%';
modal.style.height = '100%';
modal.style.zIndex = '2147483647';
}
});
};
// 監(jiān)聽彈窗顯示狀態(tài)變化
watch(() => props.visible, (newVisible) => {
if (newVisible) {
nextTick(() => {
updateTeleportTarget();
if (isFullscreen.value) {
forceRerender();
}
});
}
}, { immediate: true });
// 監(jiān)聽全屏狀態(tài)變化
watch(isFullscreen, () => {
if (props.visible) {
nextTick(() => {
forceRerender();
});
}
});
// 生命周期鉤子
onMounted(() => {
isFullscreen.value = !!document.fullscreenElement;
fullscreenElement.value = document.fullscreenElement;
updateTeleportTarget();
document.addEventListener('fullscreenchange', handleFullscreenChange);
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
document.addEventListener('mozfullscreenchange', handleFullscreenChange);
document.addEventListener('MSFullscreenChange', handleFullscreenChange);
});
onUnmounted(() => {
document.removeEventListener('fullscreenchange', handleFullscreenChange);
document.removeEventListener('webkitfullscreenchange', handleFullscreenChange);
document.removeEventListener('mozfullscreenchange', handleFullscreenChange);
document.removeEventListener('MSFullscreenChange', handleFullscreenChange);
});
</script>
<style scoped>
.fullscreen-modal-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(8px);
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
z-index: 9999;
}
.fullscreen-modal-fullscreen {
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
width: 100% !important;
height: 100% !important;
z-index: 2147483647 !important;
background: rgba(0, 0, 0, 0.5) !important;
backdrop-filter: blur(8px) !important;
}
.fullscreen-modal-content {
background: white;
border-radius: 1rem;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
width: 100%;
max-width: 80rem;
height: auto;
min-height: 500px;
position: relative;
z-index: 10;
overflow: hidden;
}
.fullscreen-modal-fullscreen .fullscreen-modal-content {
max-width: 90vw;
max-height: 90vh;
}
.fullscreen-modal-container {
visibility: visible !important;
opacity: 1 !important;
pointer-events: auto !important;
display: flex !important;
}
*:fullscreen .fullscreen-modal-container,
*:-webkit-full-screen .fullscreen-modal-container,
*:-moz-full-screen .fullscreen-modal-container {
position: absolute !important;
z-index: 999999 !important;
background: rgba(0, 0, 0, 0.5) !important;
backdrop-filter: blur(8px) !important;
}
@media (max-width: 768px) {
.fullscreen-modal-content {
max-width: 95%;
margin: 0.5rem;
}
.fullscreen-modal-fullscreen .fullscreen-modal-content {
max-width: 95vw;
max-height: 85vh;
}
}
</style>關(guān)鍵要點(diǎn)總結(jié)
必須要做的
- 監(jiān)聽多種全屏事件:兼容不同瀏覽器
- 動(dòng)態(tài)切換 Teleport 目標(biāo):全屏?xí)r掛載到全屏元素
- 使用最高 z-index:
2147483647 - 強(qiáng)制樣式覆蓋:使用
!important - 添加備用渲染方案:確保萬無一失
常見錯(cuò)誤
- 只監(jiān)聽標(biāo)準(zhǔn)全屏事件:忽略瀏覽器兼容性
- 固定 Teleport 到 body:全屏?xí)r無法顯示
- z-index 不夠高:被全屏元素覆蓋
- 忘記強(qiáng)制樣式:可能被其他樣式干擾
- 沒有響應(yīng)式處理:移動(dòng)設(shè)備顯示異常
調(diào)試技巧
- 添加調(diào)試信息:
<!-- 開發(fā)時(shí)啟用 -->
<div class="debug-info">
<div>全屏狀態(tài): {{ isFullscreen ? '是' : '否' }}</div>
<div>Teleport目標(biāo): {{ teleportTarget }}</div>
<div>全屏元素: {{ fullscreenElement?.tagName || '無' }}</div>
</div>- 控制臺(tái)日志:監(jiān)控狀態(tài)變化
- 檢查元素位置:開發(fā)者工具查看層級(jí)
- 測試多種場景:不同設(shè)備、瀏覽器
適用場景
- 彈窗對(duì)話框
- 遮罩層
- 通知組件
- 加載狀態(tài)
- 確認(rèn)框
- 側(cè)邊欄
- 任何需要在全屏模式下顯示的浮層組件
瀏覽器兼容性
| 瀏覽器 | 支持程度 | 注意事項(xiàng) |
|---|---|---|
| Chrome | ? 完全支持 | 推薦 |
| Firefox | ? 完全支持 | 推薦 |
| Safari | ? 完全支持 | 需要 webkit 前綴 |
| Edge | ? 完全支持 | 現(xiàn)代版本 |
| IE11 | ?? 部分支持 | 需要 MS 前綴 |
記?。哼@個(gè)解決方案的核心是動(dòng)態(tài)適應(yīng)全屏狀態(tài),通過 Teleport 和強(qiáng)制樣式確保彈窗始終可見!
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
vue組件中iview的modal組件爬坑問題之modal的顯示與否應(yīng)該是使用v-show
這篇文章主要介紹了vue組件中iview的modal組件爬坑問題之modal的顯示與否應(yīng)該是使用v-show,本文通過實(shí)例圖文相結(jié)合的形式給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04
vue3通過組合鍵實(shí)現(xiàn)換行操作的示例詳解
這篇文章主要為大家詳細(xì)介紹了vue3如何通過組合鍵,例如command+Enter、shift+Enter、alt + Enter,實(shí)現(xiàn)換行操作,感興趣的可以了解下2024-03-03
Vue3+TS實(shí)現(xiàn)數(shù)字滾動(dòng)效果CountTo組件
最近開發(fā)有個(gè)需求需要酷炫的文字滾動(dòng)效果,發(fā)現(xiàn)vue2版本的CountTo組件不適用與Vue3,沒有輪子咋辦,那咱造一個(gè)唄,感興趣的小伙伴可以跟隨小編一起了解一下2022-11-11
使用Vue封裝一個(gè)可隨時(shí)暫停啟動(dòng)無需擔(dān)心副作用的定時(shí)器
這篇文章主要為大家詳細(xì)介紹了如何使用Vue封裝一個(gè)可隨時(shí)暫停啟動(dòng)無需擔(dān)心副作用的定時(shí)器,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-11-11
Vue電商網(wǎng)站首頁內(nèi)容吸頂功能實(shí)現(xiàn)過程
電商網(wǎng)站的首頁內(nèi)容會(huì)比較多,頁面比較長,為了能讓用戶在滾動(dòng)瀏覽內(nèi)容的過程中都能夠快速的切換到其它分類。需要分類導(dǎo)航一直可見,所以需要一個(gè)吸頂導(dǎo)航的效果。目標(biāo):完成頭部組件吸頂效果的實(shí)現(xiàn)2023-04-04
Vue實(shí)現(xiàn)todo應(yīng)用的示例
這篇文章主要介紹了Vue實(shí)現(xiàn)todo應(yīng)用的示例,幫助大家更好的理解和學(xué)習(xí)使用vue框架,感興趣的朋友可以了解下2021-02-02

