1. 全屏 API 到底能做什么,为什么值得单独拿出来讲
第一次接触 Fullscreen API 的人,多半是因为一个很具体的需求:页面上有个视频播放器,用户点一下按钮,希望它铺满整个屏幕,而不是只占页面里那一小块区域。或者是在做一个在线演示工具、一个网页游戏、一个数据大屏,需要把某个元素放大到全屏状态,把浏览器地址栏、标签栏、书签栏统统藏起来,让用户获得沉浸式体验。
这个需求听起来简单,但真动手做的时候,坑比想象中多。不同浏览器前缀不一样、不同元素的请求方式有差异、退出全屏的事件监听容易写错、移动端和桌面端行为不一致、iframe 里嵌的页面想全屏还得额外加属性。我见过不少项目,全屏功能写是写出来了,但在某些机型上就是没反应,或者退出后页面布局错乱,排查半天找不到原因。
Fullscreen API 就是浏览器提供的一套标准接口,让开发者可以用 JavaScript 控制某个元素进入和退出全屏状态。它不是什么新东西,但真正把它用对、用稳,需要理解它的请求模型、权限模型、事件模型和兼容性处理。这篇文章我会从实际项目出发,把 Fullscreen API 的核心机制、实操步骤、参数细节、常见坑和排查方法全部拆开讲清楚。不管你是刚接触前端的新手,还是做过几个项目但全屏这块总是靠复制粘贴的老手,都能从里面拿到能直接用的东西。
我下面讲的内容,全部基于标准 Web API 的通用实践,不涉及任何特定平台或框架的私有实现。你可以把它当成一份全屏功能的实操手册,照着做就能落地。
2. 全屏 API 的核心机制与请求模型拆解
2.1 为什么全屏要设计成“请求-授权”模式
很多人第一次看 Fullscreen API 的文档会有点困惑:为什么不能直接设置一个元素的样式让它全屏,非要调用一个requestFullscreen()方法,还要处理 Promise 和事件?
这背后的逻辑是安全和用户体验的平衡。全屏状态会覆盖整个屏幕,隐藏浏览器自身的 UI,如果任何网页都能在用户不知情的情况下自动进入全屏,那用户就会失去对浏览器的控制权,钓鱼网站可以伪装成系统界面,恶意页面可以锁死用户的退出路径。所以浏览器把全屏设计成一种需要“用户主动触发”的权限行为。
具体来说,requestFullscreen()必须在用户手势(user gesture)的回调里调用,比如 click、touch、keydown 这些事件的处理器内部。如果你在 setTimeout 里延迟调用,或者在页面加载完成后自动调用,浏览器会直接拒绝,并在控制台抛出错误。这个限制是硬性的,没有绕过的方法,也不应该有绕过的方法。
理解这一点之后,很多“为什么我的全屏没反应”的问题就迎刃而解了。不是代码写错了,而是调用时机不对。
2.2 请求对象:到底该对哪个元素调用
Fullscreen API 的一个关键设计是:全屏是针对“元素”的,不是针对整个页面的。你可以让document.documentElement全屏,也可以让某个div、某个video、某个canvas全屏。
这个设计带来的灵活性很重要。比如一个页面里同时有视频区和评论区,你只想让视频区全屏,那就对视频容器调用requestFullscreen(),而不是对整个文档调用。全屏之后,只有那个元素及其子元素可见,页面其他部分会被隐藏。
这里有个容易踩的坑:如果你对一个元素调用了全屏,然后在全屏状态下又对另一个元素调用全屏,浏览器会先退出当前全屏,再进入新的全屏。这个切换过程会触发相应的事件,如果你的退出逻辑写得比较重,可能会出现闪烁或状态错乱。所以在一个页面里,最好统一管理全屏的目标元素,避免多处随意调用。
另外,全屏元素的 CSS 表现也需要注意。进入全屏后,浏览器会给该元素应用一套默认样式,比如width: 100%、height: 100%、背景变黑等。如果你原本的布局依赖父容器的尺寸,全屏后父容器不在了,元素可能会塌陷或者拉伸变形。解决办法是在全屏状态下用:fullscreen伪类单独写一套样式,把尺寸和布局固定下来。
2.3 标准方法与浏览器前缀的历史包袱
Fullscreen API 的发展经历了一段前缀混战的时期。早期各浏览器实现不一样,有webkitRequestFullscreen、mozRequestFullScreen、msRequestFullscreen等多种写法,连大小写都不统一。虽然现在主流浏览器基本都支持标准方法,但在一些老版本或者特定内核的环境里,前缀方法仍然需要兼容。
我一般会封装一个统一的工具函数,按优先级依次尝试标准方法和前缀方法。这样业务代码里只需要调用一个入口,不用到处写 if-else。下面是一个常见的封装思路:
function requestFullscreen(element) { const el = element || document.documentElement; const request = el.requestFullscreen || el.webkitRequestFullscreen || el.mozRequestFullScreen || el.msRequestFullscreen; if (!request) { console.warn('当前环境不支持全屏 API'); return Promise.reject(new Error('Fullscreen not supported')); } try { const result = request.call(el); // 标准方法返回 Promise,前缀方法可能返回 undefined return result instanceof Promise ? result : Promise.resolve(); } catch (err) { return Promise.reject(err); } }这段代码的关键点在于:标准方法返回 Promise,可以链式处理成功和失败;前缀方法大多不返回 Promise,所以需要手动包一层。另外request.call(el)是必须的,因为方法内部的 this 指向必须是那个元素,直接赋值给变量再调用会丢失上下文。
退出全屏的方法也是类似的封装逻辑,标准是document.exitFullscreen(),前缀有document.webkitExitFullscreen()等。注意退出全屏是挂在 document 上的,不是挂在元素上的,这一点和请求全屏不一样。
2.4 全屏状态检测:为什么不能只靠一个布尔值
判断当前是否处于全屏状态,标准属性是document.fullscreenElement。如果这个值不为 null,说明有元素处于全屏;如果为 null,说明没有全屏。前缀版本有document.webkitFullscreenElement等。
很多人的第一反应是维护一个isFullscreen布尔变量,在进入和退出时手动切换。这种做法在简单场景下能用,但一旦涉及多个元素、多次切换、用户按 ESC 退出,就容易出现状态不一致。因为用户可以通过 ESC 键、手势、系统返回键等多种方式退出全屏,这些操作不一定经过你的代码,你手动维护的变量就失效了。
正确的做法是始终以document.fullscreenElement为准,在fullscreenchange事件里读取这个值来更新 UI 状态。这样无论用户怎么退出,你的状态都能同步。我踩过这个坑:一个播放器页面,用户按 ESC 退出全屏后,页面上的全屏按钮图标没有变回来,用户再点一次反而又进入了全屏,体验很割裂。后来改成监听事件读取真实状态,问题就消失了。
3. 实操落地:从零实现一个稳定的全屏功能
3.1 最小可用示例:一个按钮控制进入和退出
先看一个最基础的实现,把核心流程跑通。假设页面上有一个视频容器和一个按钮,点击按钮切换全屏状态。
<div id="player" class="player"> <video src="demo.mp4" controls></video> </div> <button id="toggleBtn">全屏</button>const player = document.getElementById('player'); const toggleBtn = document.getElementById('toggleBtn'); function getFullscreenElement() { return document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement || null; } function requestFullscreen(el) { const request = el.requestFullscreen || el.webkitRequestFullscreen || el.mozRequestFullScreen || el.msRequestFullscreen; if (!request) return Promise.reject(new Error('不支持全屏')); const result = request.call(el); return result instanceof Promise ? result : Promise.resolve(); } function exitFullscreen() { const exit = document.exitFullscreen || document.webkitExitFullscreen || document.mozCancelFullScreen || document.msExitFullscreen; if (!exit) return Promise.reject(new Error('不支持退出全屏')); const result = exit.call(document); return result instanceof Promise ? result : Promise.resolve(); } toggleBtn.addEventListener('click', async () => { try { if (getFullscreenElement()) { await exitFullscreen(); } else { await requestFullscreen(player); } } catch (err) { console.error('全屏操作失败:', err); } }); function handleFullscreenChange() { const isFull = !!getFullscreenElement(); toggleBtn.textContent = isFull ? '退出全屏' : '全屏'; } document.addEventListener('fullscreenchange', handleFullscreenChange); document.addEventListener('webkitfullscreenchange', handleFullscreenChange); document.addEventListener('mozfullscreenchange', handleFullscreenChange); document.addEventListener('MSFullscreenChange', handleFullscreenChange);这段代码虽然不长,但包含了几个关键点:统一的状态检测函数、带前缀兼容的请求和退出封装、在用户手势里调用、监听所有前缀的 change 事件来同步按钮文案。把这几个点做到位,基本功能就稳了。
3.2 全屏样式处理::fullscreen伪类的正确用法
进入全屏后,浏览器会给全屏元素应用默认样式,但这个默认样式往往不够用。比如一个视频容器,原本在页面里是固定宽高的,全屏后你希望视频居中、背景黑色、控制条位置合适,这些都需要自己写。
标准伪类是:fullscreen,前缀版本有:-webkit-full-screen、:-moz-full-screen、:-ms-fullscreen。注意前缀伪类的写法差异,webkit 是两个短横线加 full-screen,moz 类似,ms 是 fullscreen 没有短横线。
.player:fullscreen { width: 100%; height: 100%; background: #000; display: flex; align-items: center; justify-content: center; } .player:fullscreen video { max-width: 100%; max-height: 100%; } .player:-webkit-full-screen { width: 100%; height: 100%; background: #000; } .player:-moz-full-screen { width: 100%; height: 100%; background: #000; }这里有个细节:全屏元素的背景色默认是黑色,但如果你不显式设置,某些浏览器可能会保留元素原本的背景,导致视觉不统一。另外全屏状态下,元素会脱离原来的文档流,父级的 flex 或 grid 布局不再生效,所以全屏元素内部的布局要自己重新定义。
还有一个容易被忽略的点:全屏时如果元素内部有绝对定位的子元素,定位基准会变成全屏元素本身,而不是原来的父容器。如果你的布局依赖某个外层容器的定位,全屏后可能会错位。解决办法是把定位基准也一起调整,或者在全屏状态下用:fullscreen重新指定。
3.3 事件监听:fullscreenchange和fullscreenerror的分工
Fullscreen API 有两个核心事件:fullscreenchange和fullscreenerror。
fullscreenchange在进入或退出全屏时都会触发,触发时机是浏览器完成状态切换之后。注意它不区分是进入还是退出,你需要自己读取fullscreenElement来判断。这个事件不仅会在调用 API 的元素上触发,也会冒泡到 document,所以通常监听 document 就够了。
fullscreenerror在请求全屏失败时触发。失败的原因可能有很多:不是用户手势触发、元素不允许全屏、iframe 缺少 allowfullscreen 属性、浏览器策略限制等。这个事件在实际排查问题时很有用,但很多人不知道它的存在,导致出错时只能看到控制台报错,不知道具体原因。
document.addEventListener('fullscreenerror', (event) => { console.error('全屏请求失败', event); // 可以在这里给用户一个提示,或者上报日志 });我建议在开发阶段把fullscreenerror的监听加上,把错误信息打出来。上线后可以保留,用于收集异常。因为全屏失败在不同设备上的表现差异很大,有些问题只在特定机型上出现,没有日志很难定位。
另外要注意,fullscreenchange事件在有些浏览器里触发时机比较微妙。比如你连续调用两次全屏请求,第一次还没完成就调第二次,事件可能会合并或者顺序错乱。所以我在实际项目里会加一个简单的锁,避免并发调用。
3.4 iframe 场景:嵌套页面全屏的额外条件
如果你的页面是通过 iframe 嵌入的,想在 iframe 内部调用全屏,需要在 iframe 标签上加上allowfullscreen属性。这个属性告诉浏览器,这个 iframe 允许其内容进入全屏。
<iframe src="inner.html" allowfullscreen></iframe>有些场景下还需要更细粒度的权限控制,比如只允许全屏但不允许其他功能,可以用allow属性:
<iframe src="inner.html" allow="fullscreen"></iframe>如果缺少这个属性,iframe 内部调用requestFullscreen()会直接失败,触发fullscreenerror。这个坑很隐蔽,因为外层页面看起来一切正常,只有 iframe 内部的全屏按钮没反应。排查时先检查 iframe 属性,再看控制台错误。
还有一个跨域的问题:如果 iframe 和父页面不同源,父页面无法直接控制 iframe 内部的全屏状态,只能通过 postMessage 通信。这种情况下,全屏的请求和退出都要在 iframe 内部完成,父页面只负责转发用户操作。
4. 兼容性、移动端与常见问题排查
4.1 桌面端与移动端的行为差异
桌面浏览器和移动浏览器在全屏行为上有不少差异,这些差异如果不提前了解,很容易在测试阶段被打个措手不及。
桌面端进入全屏后,浏览器窗口会占据整个屏幕,地址栏、标签栏、书签栏都会隐藏。用户按 ESC 键可以退出全屏,这是浏览器的默认行为,无法禁用。退出后页面会恢复到原来的状态,但有些浏览器会触发一次 resize,如果你的页面有响应式布局,需要处理这个 resize 事件。
移动端的情况更复杂。iOS 的 Safari 对 Fullscreen API 的支持一直比较特殊,早期版本只支持 video 元素全屏,不支持任意元素全屏。较新的版本虽然支持了标准 API,但在 iPad 上行为又和 iPhone 不同。Android 各厂商浏览器内核差异也大,有的支持标准方法,有的只支持 webkit 前缀。
我的经验是:移动端不要假设全屏一定能用,要做好降级方案。比如视频播放,如果全屏 API 不可用,可以退而求其次用 video 元素自带的webkitEnterFullscreen方法,或者干脆用 CSS 把视频放大到视口大小,模拟全屏效果。虽然不如真全屏沉浸,但至少功能可用。
另外移动端全屏后,屏幕方向可能会自动旋转,或者用户手动旋转屏幕。你需要监听orientationchange事件,在全屏状态下调整布局。有些游戏类应用会强制横屏,这需要额外的屏幕方向锁定 API,但那个 API 的支持度也不统一,需要谨慎使用。
4.2 常见问题速查表
下面这张表整理了我在实际项目中遇到的全屏相关问题,以及对应的排查思路和解决方法。
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 调用 requestFullscreen 无反应 | 不是用户手势触发 | 检查调用是否在 click 等事件回调内 | 把调用移到用户手势回调中 |
| 控制台报错 “Failed to execute” | 元素类型不支持或权限不足 | 查看 fullscreenerror 事件详情 | 确认元素可全屏,检查 iframe 属性 |
| 全屏后布局错乱 | 全屏元素脱离原文档流 | 检查全屏元素的 CSS 尺寸和定位 | 用 :fullscreen 伪类重写样式 |
| 退出全屏后按钮状态不对 | 手动维护的状态未同步 | 检查是否监听 fullscreenchange | 以 fullscreenElement 为准更新 UI |
| iframe 内全屏失效 | 缺少 allowfullscreen 属性 | 检查 iframe 标签属性 | 添加 allowfullscreen 或 allow=“fullscreen” |
| 移动端全屏后无法退出 | 系统手势与浏览器行为冲突 | 测试不同机型的退出方式 | 提供显式的退出按钮,不依赖 ESC |
| 连续调用全屏出现闪烁 | 并发请求导致状态切换 | 加锁避免重复调用 | 用标志位控制请求节奏 |
| 全屏后视频比例变形 | 未设置 object-fit | 检查 video 的 CSS | 设置 object-fit: contain 或 cover |
这张表里的每一条都是我或者身边同事真实踩过的坑。比如“连续调用全屏出现闪烁”这一条,当时是一个轮播图组件,每次切换 slide 都调用全屏,快速切换时页面疯狂闪烁。后来加了一个isRequesting标志位,请求完成前忽略新的请求,问题就解决了。
4.3 权限策略与浏览器限制
现代浏览器对全屏 API 还有一些额外的策略限制,这些限制在不同浏览器里表现不一样,需要特别注意。
首先是权限策略(Permissions Policy)。如果你的页面嵌在 iframe 里,父页面可以通过allow属性控制是否授予全屏权限。如果父页面没有授予,iframe 内部调用全屏会失败。这个策略是为了防止恶意 iframe 滥用全屏。
其次是用户激活(User Activation)的要求。前面提过,全屏请求必须在用户手势的回调里调用。但“用户手势”的定义在不同浏览器里略有差异。比如 click 事件肯定算,但 mousemove 不算,keydown 算但 keyup 可能不算。如果你在异步操作之后调用全屏,比如先 fetch 数据再全屏,那 fetch 的回调已经脱离了用户手势的上下文,全屏会失败。解决办法是在用户点击时先进入全屏,再异步加载数据。
还有一个限制是:全屏状态下,某些浏览器会限制弹出新窗口或触发下载。如果你的全屏应用里有这些操作,需要提前测试。我遇到过一个案例,全屏状态下点击下载按钮没反应,退出全屏后就正常了。后来查资料发现是浏览器的安全策略,全屏时禁止触发下载,防止用户被误导。
4.4 实操心得与避坑建议
做了几个全屏相关的项目之后,我总结了一些文档里不会写、但实际开发中很有用的经验。
第一,全屏按钮的文案和图标要跟随真实状态变化。不要自己维护一个变量,而是监听fullscreenchange事件,读取fullscreenElement来更新。这样无论用户是点按钮退出,还是按 ESC 退出,还是通过系统手势退出,UI 都能保持一致。
第二,全屏请求要加错误处理。requestFullscreen()返回的 Promise 一定要 catch,否则失败时会在控制台留下未处理的 rejection,而且用户没有任何反馈。我一般会在 catch 里给一个轻提示,告诉用户当前环境不支持全屏,或者建议用其他方式查看。
第三,全屏元素的样式要单独写。不要指望原来的布局在全屏后还能用,因为全屏元素脱离了文档流,父级的尺寸约束全部失效。用:fullscreen伪类把宽高、背景、对齐方式都重新定义一遍,这样最稳。
第四,移动端要提供显式的退出按钮。虽然 ESC 键在桌面端很方便,但移动端没有 ESC,用户可能不知道怎么退出。在全屏界面上放一个明显的关闭按钮,比依赖系统手势可靠得多。
第五,测试要覆盖多种场景。不要只在 Chrome 桌面版上测,要测 Safari、Firefox、Edge,要测 iOS 和 Android,要测 iframe 嵌套的情况,要测快速连续点击的情况。全屏功能的兼容性问题往往藏在细节里,只有多测才能发现。
第六,全屏状态下的键盘事件要重新绑定。有些浏览器在全屏后,键盘事件的 target 会变成全屏元素,而不是原来的 document。如果你的快捷键监听挂在 document 上,全屏后可能失效。解决办法是把监听挂在全屏元素上,或者在进入全屏后重新绑定。
5. 进阶用法:全屏与其他 API 的配合
5.1 全屏加屏幕方向锁定
在移动端做全屏游戏或视频时,经常需要锁定屏幕方向。Screen Orientation API 可以配合 Fullscreen API 使用,在进入全屏后锁定为横屏或竖屏。
async function enterFullscreenWithOrientation(element, orientation) { await requestFullscreen(element); if (screen.orientation && screen.orientation.lock) { try { await screen.orientation.lock(orientation); } catch (err) { console.warn('屏幕方向锁定失败:', err); } } }需要注意的是,屏幕方向锁定在桌面端基本不支持,在移动端也需要全屏状态下才能生效。而且不同浏览器对 lock 方法的支持度不一样,有些需要加前缀。所以这段代码一定要包在 try-catch 里,失败了不影响全屏本身。
另外,锁定方向后,用户旋转设备时屏幕不会跟着转,这可能会让用户感到困惑。所以最好在退出全屏时解锁方向,让屏幕恢复自动旋转。
5.2 全屏状态下的画布与视频处理
如果你在全屏状态下做 canvas 绘图或视频渲染,需要注意尺寸变化。进入全屏后,canvas 的 CSS 尺寸变了,但它的绘图缓冲区尺寸(width 和 height 属性)不会自动变。如果不手动调整,画面会模糊或拉伸。
function resizeCanvas(canvas) { const dpr = window.devicePixelRatio || 1; const rect = canvas.getBoundingClientRect(); canvas.width = rect.width * dpr; canvas.height = rect.height * dpr; const ctx = canvas.getContext('2d'); ctx.scale(dpr, dpr); } document.addEventListener('fullscreenchange', () => { if (document.fullscreenElement) { resizeCanvas(myCanvas); } });这段代码的关键是考虑设备像素比(devicePixelRatio),在高分屏上如果不乘 dpr,canvas 会显得模糊。全屏后尺寸变化,需要重新计算并重绘。
视频也是类似,全屏后视频元素的尺寸变了,如果用了自定义的控制层或字幕层,需要重新计算它们的位置。我一般会在fullscreenchange事件里统一触发一次布局重算,把所有依赖尺寸的组件都更新一遍。
5.3 全屏与页面可见性、焦点管理
全屏状态下,如果用户切换到其他标签页或最小化窗口,页面会触发visibilitychange事件。有些应用需要在这种情况下暂停播放或暂停游戏,以节省资源。全屏状态本身不会因为页面不可见而自动退出,但用户回来时可能会发现状态不对。
另外,全屏后页面的焦点管理也需要注意。如果全屏元素内部有表单或可聚焦元素,进入全屏后焦点可能会丢失。我建议在全屏后主动把焦点设置到全屏元素上,或者设置到某个关键控件上,方便用户直接用键盘操作。
document.addEventListener('fullscreenchange', () => { if (document.fullscreenElement) { document.fullscreenElement.focus(); } });这个细节看起来小,但在需要键盘操作的全屏应用里(比如演示工具、游戏),焦点丢失会导致快捷键失效,用户体验很差。
5.4 全屏 API 的降级与替代方案
虽然 Fullscreen API 的支持度已经不错,但在一些特殊环境下仍然不可用。比如某些嵌入式浏览器、某些小程序的 webview、某些老旧的移动设备。这时候需要准备降级方案。
最简单的降级是用 CSS 模拟全屏:把元素设置为position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 9999;,这样元素会覆盖整个视口,虽然浏览器 UI 还在,但视觉上接近全屏。这种方案的好处是兼容性极好,坏处是无法隐藏浏览器 UI,沉浸感差一些。
对于视频,还可以用 video 元素自带的webkitEnterFullscreen()方法,这在 iOS 上比较可靠。但这个方法只对 video 元素有效,不能用于其他元素。
选择降级方案时,要根据具体场景权衡。如果是视频播放,优先用原生全屏;如果是游戏或演示,CSS 模拟全屏也能接受;如果对沉浸感要求极高,那就只能提示用户当前环境不支持,建议换浏览器或设备。
6. 写在最后的一些个人体会
全屏功能看起来简单,但真正做好需要关注的细节很多。我从最早只会复制粘贴requestFullscreen,到后来能独立处理各种兼容性和边界情况,中间踩了不少坑。最大的体会是:不要假设浏览器行为一致,不要假设用户会按你预期的方式操作,不要假设全屏状态会一直保持。
每次做全屏相关的需求,我都会问自己几个问题:用户怎么进入全屏?怎么退出全屏?退出后状态怎么同步?如果全屏失败,用户看到什么?移动端和桌面端分别怎么处理?把这些问题想清楚,代码写出来就稳了。
还有一个建议是:把全屏相关的逻辑封装成独立的模块或工具函数,不要散落在业务代码里。这样不仅复用方便,测试也容易。我现在的项目里有一个fullscreen.js,里面封装了请求、退出、状态检测、事件绑定、样式注入等所有逻辑,业务组件只需要调用几个方法就行。这样即使浏览器行为有变化,也只需要改一个地方。
最后分享一个小技巧:在开发阶段,可以在控制台手动调用document.fullscreenElement来查看当前全屏状态,用document.exitFullscreen()来强制退出。调试全屏问题时,这两个命令比反复点按钮快得多。另外,Chrome DevTools 的设备模拟器里可以模拟移动端全屏行为,虽然不完全准确,但能发现大部分布局问题。
全屏 API 本身不复杂,复杂的是它和各种环境、各种用户行为的交互。把本文里的兼容性处理、事件监听、样式重写、降级方案都做到位,你的全屏功能就能在绝大多数场景下稳定运行。剩下的就是多测、多调、多积累经验了。