fullscreenchange事件必须绑定在document上,不可绑定到body、元素或window;需用Promise.resolve().then()延迟读取document.fullscreenElement以确保状态准确,且须兼容webkit/moz/ms前缀。

fullscreenchange 事件必须绑定在 document 上
这个事件不会冒泡,也不在 window 或任意 DOM 元素上触发,只在 document 级别派发。绑错位置(比如 document.body、video 元素或 window)会导致监听完全失效。
正确写法只有这一种:
document.addEventListener('fullscreenchange', () => {
// 这里读取状态
});
- 事件名严格区分大小写:
fullscreenchange,不是fullScreenChange或fullscreenChanged - 旧版浏览器需额外监听前缀变体:
webkitfullscreenchange、mozfullscreenchange、msfullscreenchange,但现代 Chrome/Firefox/Edge 已统一支持标准名 - 不要用
onfullscreenchange属性赋值,它不可靠且无法移除
状态判断不能只靠事件回调时机
进入或退出全屏是异步过程,fullscreenchange 触发时,document.fullscreenElement 的值可能还没更新——尤其在 Safari 中延迟明显。
稳妥做法是加一层微任务延迟读取:
立即学习“前端免费学习笔记(深入)”;
document.addEventListener('fullscreenchange', () => {
Promise.resolve().then(() => {
if (document.fullscreenElement) {
console.log('已进入全屏');
} else {
console.log('已退出全屏');
}
});
});
-
document.fullscreenElement为null表示退出全屏(注意:不是undefined) - 不要依赖
document.fullscreenEnabled判断当前状态,它只表示 API 是否可用 - 兼容旧浏览器时,降级检查
document.webkitFullscreenElement和document.msFullscreenElement
video 元素的全屏切换也要走 document 监听
即使你对 video 元素调用 requestFullscreen(),状态变化事件依然只从 document 派发,不是 video 自身。
常见错误是这样写:
// ❌ 错误:监听 video 本身,永远不会触发
video.addEventListener('fullscreenchange', handler);
正确方式始终是:
// ✅ 正确:所有全屏状态变更都从 document 来
document.addEventListener('fullscreenchange', () => {
const isVideoFullscreen = document.fullscreenElement === video;
});
- 用户按 Esc、F11 或点击浏览器全屏控件,也会触发该事件
- 多个元素可能先后请求全屏,
document.fullscreenElement始终指向当前那个 - 如果
video被移除或替换,但没主动调用document.exitFullscreen(),状态可能滞留,导致判断失准
为什么 exitFullscreen() 后没触发 fullscreenchange?
调用 document.exitFullscreen() 失败时,fullscreenchange 不会触发。典型失败原因包括:
-
document.fullscreenElement已为null,再调用会抛出"Document not active"异常 - 页面已失去焦点(比如切到其他 tab),浏览器拒绝执行
- 在 iframe 中调用,但父页未设置
allow="fullscreen" - 调用发生在非用户手势上下文(如
setTimeout、fetch回调中)
安全写法是先判空再调用:
if (document.fullscreenElement) {
document.exitFullscreen().catch(e => {
console.warn('exitFullscreen failed:', e.name);
});
}
真正容易被忽略的是:Safari 对状态同步最不及时,哪怕加了 Promise.resolve().then(),有时仍需 setTimeout(() => {}, 16) 才能读到准确值。



















