一个幻灯片、一个视频播放器、一块展厅大屏、一个课堂投影上的计时器——只要浏览器顶部的标签页和地址栏还在,这些东西看起来都像没做完。前端开发者迟早会接到同一句需求:能不能占满整个屏幕?

浏览器其实自带答案,就是 Fullscreen API。但真正上线时,它总会咬你一口:用户手势限制、Safari 的前缀、死活不配合的 iPhone,以及你精心准备的全屏体验跑到第二分钟屏幕自己睡着了。

打开网易新闻 查看精彩图片

它只给你三样东西

Fullscreen API 很小,日常用到的就三个成员。第一个是 element.requestFullscreen(),请求浏览器用整块屏幕显示这个元素及其子元素,返回一个 Promise。第二个是 document.exitFullscreen(),退出全屏,同样返回 Promise。第三个是 document.fullscreenElement,指向当前处于全屏的元素,没有则为 null——这是判断"现在是不是全屏"的唯一可信来源。

此外 document 上还有两个事件:fullscreenchange 在全屏进入或退出时触发,fullscreenerror 在请求失败时触发。

有一点常让人意外:你可以让任意元素全屏,不只是整个页面。把一个 div 全屏,就只有这个 div 铺满屏幕,文档里其他内容都被它挡在后面。想让整个页面全屏,得对 document.documentElement(也就是 html 元素)调用这个方法。

切换全屏,以及那个绕不开的手势规则

最简单的可用形态是一个按钮,在进入和退出之间切换。判断 document.fullscreenElement 是否为空,为空就请求全屏,不为空就退出。两个方法都返回 Promise,所以可以 await,等过渡真正结束再执行后续代码。

但这两个调用都可能被拒绝。比如文档不被允许进入全屏,或者在没有任何元素全屏时调用 exitFullscreen()。实际代码里应该用 try/catch 包起来,而不是让一个未处理的 rejection 掉进控制台。

接下来是第一个坑,也是 Stack Overflow 上最容易让人困惑的问题:你没法自动进入全屏。页面加载时不行,定时器触发时不行,响应网络事件时也不行。

requestFullscreen() 只在页面拥有规范所说的"瞬时激活"时才有效——也就是用户真正与页面交互(点击、触摸、按键)之后的一小段窗口期。窗口之外,Promise 会被拒绝,多数浏览器会提示"API 只能由用户手势发起"。

这是有意设计的。没有这条限制,任何页面都能在你打开它的瞬间劫持整块屏幕,而这正是钓鱼页面最想干的事。

落到代码上就是两条:始终在绑定用户输入的事件处理器里触发全屏,比如 click、keydown、pointerup;不要在 requestFullscreen() 之前 await 一个耗时操作。如果先 await fetch(...),等你再请求全屏时,激活窗口可能已经过期。要么先请求全屏再做慢活,要么就接受失败。

一个很自然的做法是让键盘快捷键承担和按钮一样的工作。监听 keydown,如果用户正在输入框里打字就跳过,按下 F 键时阻止默认行为并调用切换函数。

这里有两个我第一次写时踩过的细节值得转述。第一,让全屏区域本身可被键盘聚焦:给它 tabindex="0",并把 Enter 和 Space 当作点击同等对待,否则键盘用户面对的是一个没人告诉过他的按钮。第二,想清楚你的快捷键会不会和页面本身的功能撞车。有个"黑客打字机"页面,任意按键都会吐出假的终端输出,包括 F。上线头几天,用户疯狂敲键盘"入侵"时,每按一次 F 就会掉出全屏。修复只有一行:在那个页面上,F 只负责进入全屏,退出交给 Esc。

说到 Esc:你不需要自己处理它。所有浏览器都会用 Esc 退出全屏,而且你无法阻止。这是一条安全逃生通道。你能做的是对它做出反应。