必须用fullscreen api而非css模拟全屏,因后者无法响应esc、不触发fullscreenchange事件、背景页仍可滚动、焦点不自动进入,且会致后续requestfullscreen()被浏览器静默拒绝。

弹窗全屏切换不能靠简单设 width: 100vw; height: 100vh,必须用 Fullscreen API 控制真实全屏状态,否则会丢失焦点管理、ESC 退出、跨标签页行为等关键能力。
为什么直接用 CSS 模拟全屏行不通
所谓“CSS 全屏”只是把弹窗拉满视口尺寸,但本质仍是普通 DOM 元素:无法响应 ESC 键、不触发 fullscreenchange 事件、背景页面仍可滚动、焦点不会自动进入弹窗、移动端 Safari 里甚至可能被系统手势劫持。更严重的是,它绕过了浏览器的全屏安全策略,导致后续想调用 requestFullscreen() 时被静默拒绝——因为浏览器认为“你已经自己搞了个假全屏”。
requestFullscreen() 必须作用于弹窗容器本身
不要对 document.body 或 document.documentElement 调用,Safari 会直接拒绝;也不能对 display: none 或未挂载的元素调用。正确做法是:
- 确保弹窗 DOM 已渲染且
offsetParent !== null - 给弹窗最外层 wrapper(如
<div class="modal-wrapper">)设 <code>id="modal-fullscreen-target" - 在按钮 click 回调中调用:
const target = document.getElementById('modal-fullscreen-target');<br>target.requestFullscreen().catch(e => {<br> if (e.name === 'NotAllowedError') console.warn('需用户点击触发');<br>}); - 若弹窗由 Vue/React 渲染,确保调用时机在组件已挂载、DOM 可见之后(如
nextTick或useEffect(() => {}, [])) - 监听
document.addEventListener('fullscreenchange', ...),而非目标元素 - 每次触发时读取
document.fullscreenElement,对比是否等于你的弹窗容器 - Safari 中状态更新有微延迟,建议用
Promise.resolve().then(...)延迟读取,避免取到旧值 - 按钮文案切换逻辑应基于该判断,例如:
if (document.fullscreenElement === target) {<br> button.textContent = '退出全屏';<br>} else {<br> button.textContent = '全屏显示';<br>} - 先判空:
if (document.fullscreenElement) {<br> document.exitFullscreen().catch(e => {<br> if (e.name !== 'NotFoundError') console.error(e);<br> });<br>} - 不要在弹窗关闭回调里无条件调用
exitFullscreen(),要先检查状态 - 若弹窗关闭时正处于全屏态,退出全屏后记得恢复
body的overflow(如果之前锁过滚动) - 移动端注意:部分 Android Chrome 版本在全屏退出后可能残留状态栏遮挡,需手动触发一次
window.scrollTo(0, 0)
如何可靠判断和同步 UI 状态
document.fullscreenElement 是唯一可信依据,它返回当前全屏的 DOM 元素,退出后为 null(不是 undefined)。别维护布尔变量,也别监听元素自身的事件——必须绑定在 document 上:
退出全屏必须捕获 NotFoundError
document.exitFullscreen() 在未全屏时调用会抛出 NotFoundError,虽不中断脚本,但会污染控制台。安全写法是:
真正难的不是调用 API,而是把弹窗生命周期(打开 → 全屏 → 退出 → 关闭)和浏览器全屏状态机对齐;稍有错位,就会出现 ESC 失效、按钮状态错乱、或全屏后无法再点开新弹窗的问题。











