← 返回
IT技术

如何在 JavaScript 中使用 Fullscreen API(并使用 Wake Lock API 保持屏幕唤醒)

✍️ zhirenhun 📅 2026/10/1 👁 213 阅读 ⏱ 46 分钟
如何在 JavaScript 中使用 Fullscreen API(并使用 Wake Lock API 保持屏幕唤醒)

迟早,大多数前端开发者都会遇到同样的需求:"这能占满整个屏幕吗?"

幻灯片、视频播放器、信息亭仪表盘、游戏、绘图画布或投影仪上的计时器,在浏览器的标签页和地址栏仍然占据边缘时,都会感觉像是半成品。

好消息是浏览器内置了答案:Fullscreen API。不太好的消息是它伴随着一些怪癖,第一次使用时就会咬到你:用户手势、Safari 前缀、直接拒绝的 iPhone,以及在你美妙的全屏体验两分钟后进入睡眠的屏幕。

在本教程中,你将学习如何:

这里的所有内容都是纯原生 JavaScript,不需要任何库或构建步骤。你应该熟悉 DOM 事件和 async/await。如果你需要对后者进行复习,freeCodeCamp 提供了一个扎实的 async/await 教程,涵盖了我们将依赖的所有内容。

目录

Fullscreen API 的工作原理

Fullscreen API 很小巧。它为你日常使用提供了三个功能:

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

令人惊讶的是,你可以让 任何 元素进入全屏,而不仅仅是整个页面。如果你对一个

请求全屏,只有该
会占满屏幕。文档中的其他内容会被它遮挡。

要让整个页面进入全屏,需要在 document.documentElement(即 元素)上调用该方法。

完整参考文档位于 MDN 的 Fullscreen API 页面,但你不需要超出上面内容的更多信息。

进入和退出全屏

让我们从最简单实用的东西开始:一个可以切换页面进入和退出全屏的按钮。


const toggleBtn = document.getElementById('fs-toggle');

async function toggleFullscreen() {
  if (!document.fullscreenElement) {
    // Nothing is fullscreen yet – enter it.
    await document.documentElement.requestFullscreen();
  } else {
    // Something is fullscreen – leave it.
    await document.exitFullscreen();
  }
}

toggleBtn.addEventListener('click', toggleFullscreen);

这就是幸福路径的全部内容。因为这两个方法都返回 Promise,你可以使用 await 等待它们完成,随后再执行任何后续代码。

不过,这两个调用也可能会被拒绝。例如,如果文档不允许进入全屏(稍后会说明原因),或者在没有任何元素处于全屏状态时调用 exitFullscreen()。因此,在实际代码中,应该用 try/catch 包裹它们,而不是让未处理的拒绝直接打印到控制台:

async function toggleFullscreen() {
  try {
    if (!document.fullscreenElement) {
      await document.documentElement.requestFullscreen();
    } else {
      await document.exitFullscreen();
    }
  } catch (err) {
    console.warn(`Fullscreen failed: ${err.name} – ${err.message}`);
  }
}

用户手势规则

这里有一个第一个陷阱,也是在 Stack Overflow 上引发最多困惑问题的地方:你不能自动进入全屏。页面加载时、定时器之后或响应网络事件时都不行。

requestFullscreen() 只有在页面具备规范所说的 瞬时激活 时才有效:用户真实与页面交互(如点击、轻触或按键)后的一段短暂时间窗口。超出该窗口,Promise 会被拒绝,而在大多数浏览器中你会看到类似 “API 只能由用户手势发起。” 的提示。

这是故意设计的。如果没有这一限制,任何页面在加载瞬间就能劫持整个屏幕,这正是钓鱼页面所渴望的行为。

实际上,这对你的代码意味着两点:

  1. 始终在与用户输入绑定的事件处理程序中触发全屏:例如 click、keydown、pointerup 等。

  2. 不要在 requestFullscreen() 调用之前放置一个用于慢速操作的 await。如果你先 await fetch(...),那么在你请求全屏时激活窗口可能已经过期。

这张图解释了用户界面中的“请求全屏”规则,说明只有在短暂的激活窗口内才会触发“请求全屏”动作,否则会被拒绝

先调用 requestFullscreen(),随后再处理耗时操作;否则激活窗口可能在你提出请求之前就关闭了。

一种常见且非常自然的做法是让键盘快捷键完成与按钮相同的工作:

document.addEventListener('keydown', (e) => {
  // Ignore shortcuts while the user is typing in a field.
  if (e.target.matches('input, textarea, [contenteditable]')) return;

  if (e.key === 'f' || e.key === 'F') {
    e.preventDefault();
    toggleFullscreen();
  }
});

我第一次犯的两个小错误值得分享。

首先,让全屏表面本身可通过键盘访问:给它 tabindex="0",并将其上的 Enter 和 Space 视作点击处理,否则键盘用户会有一个他们不知道的按钮。

其次,考虑你的快捷键是否与页面实际功能冲突。我有一个“黑客打字员”页面,任何按键都会输出假终端输出,包括 F。最初几天,用户狂打时每次按到该字母就会在“黑客”过程中退出全屏。解决办法很简单:在该页面,F 只会 进入 全屏,而退出则完全由 Esc 负责。

这就提到了 Esc:你不需要自己处理它。每个浏览器在按下 Esc 时会退出全屏,你无法阻止。这是一个安全的逃生出口。你 可以 做的是对此作出反应,这就是下一节的内容。

通过 fullscreenchange 保持 UI 与全屏同步

因为用户可以通过你的代码无法控制的方式退出全屏(例如按下 Esc、使用浏览器自带的退出按钮,或在移动设备上切换应用),你永不应在自己的变量中跟踪全屏状态。这样会导致状态不同步。

相反,将 document.fullscreenElement 视为事实,并在每次触发 fullscreenchange 时更新 UI:

function syncFullscreenUI() {
  const isFullscreen = Boolean(document.fullscreenElement);
  toggleBtn.textContent = isFullscreen ? 'Exit fullscreen' : 'Fullscreen';
  toggleBtn.setAttribute('aria-pressed', String(isFullscreen));
  document.body.classList.toggle('is-fullscreen', isFullscreen);
}

document.addEventListener('fullscreenchange', syncFullscreenUI);
document.addEventListener('fullscreenerror', () => {
  console.warn('Could not enter fullscreen.');
});

这个单一的监听器覆盖了进入和退出全屏的所有路径,即便是你未主动触发的情况也包括在内。它也是暂停动画、恢复游戏循环或(稍后你会看到)释放唤醒锁的理想位置。

全屏内容的样式

CSS 提供了两个用于全屏状态的钩子。

:fullscreen 伪类匹配当前处于全屏状态的元素。浏览器会应用一个默认样式表,把元素拉伸以填满整个视口,但通常你会想自己控制背景、溢出等属性:

:fullscreen {
  background: #000;
  overflow: hidden;
  cursor: none; /* hide the pointer on an idle fullscreen surface */
}

那行 cursor: none 看起来只是装饰,直到你在 OLED 屏幕上显示纯黑页面:每个像素都熄灭,鼠标指针实际上是面板上唯一发光的东西。当页面进入全屏时隐藏它,这就是 “屏幕已关闭” 与 “屏幕中央有一个微小的白色箭头” 之间的区别。

::backdrop 伪元素是绘制在全屏元素 背后 的层。只有当全屏元素未覆盖整个屏幕时(例如,具有固定宽高比的元素),它才会起作用,这就是你控制信箱着色的方式:

:fullscreen::backdrop {
  background: #000;
}

一个实用的技巧:如果你要全屏显示整个页面,请将背景颜色设置在 html 上,而不是 body。全屏时,html 是被显示的元素;在某些浏览器中,仅在 body 上设置背景会在过渡期间导致边缘出现一条错误颜色的细条。

跨浏览器注意事项

较旧的 Safari 需要 webkit 前缀

Safari 仅在 macOS 和 iPadOS 的版本 16.4(2023 年春季)中发布了未加前缀的 API。在此之前,它使用 webkitRequestFullscreen()、webkitExitFullscreen()、webkitFullscreenElement 以及 webkitfullscreenchange 事件。除非你能够忽略三年前的 Safari 安装版本,否则添加一个微小的兼容层是值得的:

const fs = {
  get element() {
    return document.fullscreenElement ?? document.webkitFullscreenElement ?? null;
  },
  get enabled() {
    return Boolean(document.fullscreenEnabled ?? document.webkitFullscreenEnabled);
  },
  request(el = document.documentElement) {
    if (el.requestFullscreen) return el.requestFullscreen();
    if (el.webkitRequestFullscreen) return Promise.resolve(el.webkitRequestFullscreen());
    return Promise.reject(new Error('Fullscreen not supported'));
  },
  exit() {
    if (document.exitFullscreen) return document.exitFullscreen();
    if (document.webkitExitFullscreen) return Promise.resolve(document.webkitExitFullscreen());
    return Promise.reject(new Error('Fullscreen not supported'));
  },
};

// Listen to both event names so old Safari stays in sync too.
['fullscreenchange', 'webkitfullscreenchange'].forEach((evt) =>
  document.addEventListener(evt, syncFullscreenUI)
);

现在 fs.request() 和 fs.exit() 在所有浏览器中都返回一个 Promise,而你的其余代码不关心它实际上在哪个环境中运行。(你会看到推荐使用 screenfull.js 来实现这一点。这是一个不错的库,但它已经被声明为功能完整并冻结,上面的十五行代码就是它曾经为你所做的全部。)

iPhone Safari 不支持元素全屏

这是个会让所有人措手不及的陷阱。截至撰写本文时,Safari 在 iPhone 上不支持对任意元素使用全屏 API – 仅适用于 。它在 iPad 和 Mac 上有效,但在手机上 document.fullscreenEnabled 的值为 false,且 requestFullscreen 在

上不存在。iOS 上的 Chrome 和 Firefox 受到同样的限制,因为它们必须使用 Safari 的引擎。

你有两种可行的方案:

  1. 一种“伪全屏”回退方案:将你的元素定位为 position: fixed; inset: 0 并隐藏你自己的浏览器界面。你无法摆脱 Safari 的地址栏,但它会在滚动时折叠,对大多数工具来说这已经足够好。

  2. 建议“添加到主屏幕”:在主屏幕上启动且其 manifest 中包含 "display": "standalone" 的网页应用将在没有任何浏览器 UI 的情况下运行。这是在 iPhone 上实现真正全屏内容的唯一方法。

无论你选择哪种方案,不要仅依赖特性检测。document.fullscreenEnabled 告诉你 API 是否 存在,但请求仍可能在运行时被拒绝(例如在没有 allowfullscreen 属性的 iframe 内部,或在浏览器策略已锁定的页面上,或者仅仅是因为你未测试过的浏览器有其自己的看法)。

对我来说,行之有效的做法是尝试使用真实的 API,并将拒绝视为触发 CSS 回退的条件:

function enterFullscreen(el) {
  if (fs.enabled) {
    return fs.request(el).catch(() => {
      // The API exists but refused – degrade to a fixed overlay.
      document.body.classList.add('pseudo-fullscreen');
    });
  }
  document.body.classList.add('pseudo-fullscreen');
  return Promise.resolve();
}

在手机上,我会再进一步,根本不调用该 API,即便在 Android 上它技术上可用。地址栏在滚动时重新出现以及系统的滑动手势之间,固定覆盖层在小触摸屏上的表现比真正的全屏更可预测。这实际上也是 iPhone Safari 强制你走的同一条代码路径。如此一来,测试分支就少了一条。

您可以在决定需要多少此功能之前,先查看 可以使用吗 上的最新支持表。

使用 Wake Lock API 保持屏幕唤醒

您已经构建了一个美妙的全屏体验。用户靠后观看……九十秒后,屏幕变暗并锁定,因为操作系统未检测到任何输入,认为没有人在场。

对于 video 元素,浏览器会为您处理此问题。对于其他情况(如倒计时、幻灯片、Canvas 动画、食谱或乐谱页面),您需要使用 屏幕唤醒 API。它在 2024 年已进入所有主要浏览器(Chrome 84+、Safari 16.4+、Firefox 126+),并于 2025 年成为 基线 的一部分,因此您可以今天直接使用,无需 polyfill。

该 API 只需一次调用,并且基于 Promise:

let wakeLock = null;

async function requestWakeLock() {
  if (!('wakeLock' in navigator)) return; // unsupported – fail silently

  try {
    wakeLock = await navigator.wakeLock.request('screen');
    wakeLock.addEventListener('release', () => {
      // The OS or browser released it (tab hidden, battery saver, etc.)
      wakeLock = null;
    });
  } catch (err) {
    // Rejected – most often because the device is in a power-saving mode.
    console.warn(`Wake lock failed: ${err.name} – ${err.message}`);
  }
}

async function releaseWakeLock() {
  if (wakeLock) {
    await wakeLock.release();
    wakeLock = null;
  }
}

以下是三条需要了解的规则:

  1. 它需要安全上下文。 该 API 仅在 HTTPS(以及 localhost)下暴露。

  2. 当页面被隐藏时,浏览器会自动释放锁。 如果用户切换标签页、最小化窗口或锁定手机,当页面再次可见时,锁不会自行恢复。您需要重新请求它:

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible' && shouldStayAwake()) {
    requestWakeLock();
  }
});
  1. 做个好公民。 唤醒锁会耗尽电池。只有在它真正有用的时候才保持它,比如某东西在播放、运行或被显示的时候。一旦停止,立即释放它。

好消息是,Fullscreen API 已经为 “something is being displayed” 提供了完美的信号:fullscreenchange。当用户进入全屏时,请求唤醒锁;当他们退出全屏时,释放它。

不过,全屏并不是唯一的好信号,将唤醒锁视为附加在 活动 上而非显示模式上会更有帮助。在倒计时计时器中,我会在倒计时开始时请求锁,在倒计时归零或暂停时释放它(不管页面是否全屏),因为没有人希望计时器在 90 秒时变黑。在雨声播放器中,唤醒锁会跟随音频:播放时获取锁,暂停时或睡眠定时器淡出声音时释放锁。

机制是相同的:只有 “屏幕现在应该保持唤醒吗?” 这个问题会变化。

把所有内容放在一起

这里有一个完整的模式:带键盘快捷键的全屏切换、随全屏状态变化的唤醒锁、无论用户如何离开都能保持同步的 UI,以及针对不支持此功能的浏览器的后备方案。

const toggleBtn = document.getElementById('fs-toggle');
let wakeLock = null;

/* ---------- Fullscreen compatibility layer ---------- */
const fs = {
  get element() {
    return document.fullscreenElement ?? document.webkitFullscreenElement ?? null;
  },
  get enabled() {
    return Boolean(document.fullscreenEnabled ?? document.webkitFullscreenEnabled);
  },
  request(el = document.documentElement) {
    if (el.requestFullscreen) return el.requestFullscreen();
    if (el.webkitRequestFullscreen) return Promise.resolve(el.webkitRequestFullscreen());
    return Promise.reject(new Error('Fullscreen not supported'));
  },
  exit() {
    if (document.exitFullscreen) return document.exitFullscreen();
    if (document.webkitExitFullscreen) return Promise.resolve(document.webkitExitFullscreen());
    return Promise.reject(new Error('Fullscreen not supported'));
  },
};

/* ---------- Wake lock ---------- */
async function requestWakeLock() {
  if (!('wakeLock' in navigator) || wakeLock) return;
  try {
    wakeLock = await navigator.wakeLock.request('screen');
    wakeLock.addEventListener('release', () => { wakeLock = null; });
  } catch (err) {
    console.warn(`Wake lock failed: ${err.name}`);
  }
}

async function releaseWakeLock() {
  if (!wakeLock) return;
  await wakeLock.release();
  wakeLock = null;
}

/* ---------- Toggle ---------- */
async function toggleFullscreen() {
  const pseudo = document.body.classList.contains('pseudo-fullscreen');
  try {
    if (pseudo) {
      document.body.classList.remove('pseudo-fullscreen');
      onFullscreenChange();
    } else if (!fs.element) {
      await fs.request();
    } else {
      await fs.exit();
    }
  } catch (err) {
    // The API refused (iframe policy, unsupported platform, etc.) – degrade gracefully.
    document.body.classList.add('pseudo-fullscreen');
    onFullscreenChange();
  }
}

/* ---------- Keep everything in sync ---------- */
function onFullscreenChange() {
  const active = Boolean(fs.element) || document.body.classList.contains('pseudo-fullscreen');
  toggleBtn.textContent = active ? 'Exit fullscreen' : 'Fullscreen';
  toggleBtn.setAttribute('aria-pressed', String(active));
  document.body.classList.toggle('is-fullscreen', active);

  // The screen should stay awake exactly as long as we're fullscreen.
  if (active) requestWakeLock(); else releaseWakeLock();
}

['fullscreenchange', 'webkitfullscreenchange'].forEach((evt) =>
  document.addEventListener(evt, onFullscreenChange)
);

// Re-acquire the lock if the tab was hidden and came back while fullscreen.
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'visible' && fs.element) requestWakeLock();
});

/* ---------- Wiring ---------- */
toggleBtn.addEventListener('click', toggleFullscreen);
document.addEventListener('keydown', (e) => {
  if (e.target.matches('input, textarea, [contenteditable]')) return;
  if (e.key === 'f' || e.key === 'F') { e.preventDefault(); toggleFullscreen(); }
});
// Unsupported platforms (iPhone Safari) simply take the catch branch above
// on the first click and land in pseudo-fullscreen – no user-agent sniffing needed.
// One more mobile quirk: rotating the device changes the viewport *after*
// the event fires, so re-measure your canvas or layout a beat later.
const resizeStage = () => { /* re-measure whatever fills the screen */ };
window.addEventListener('orientationchange', () => setTimeout(resizeStage, 100));

对应的 CSS 如下:

html { background: #000; }

:fullscreen { overflow: hidden; cursor: none; }
:fullscreen::backdrop { background: #000; }

/* Fallback for browsers without element fullscreen */
body.pseudo-fullscreen .app { position: fixed; inset: 0; }
body.pseudo-fullscreen .chrome { display: none; }

大约六十行代码,这就是我在生产中运行的同一核心:我将每次全屏进入记录为一个分析事件,因为在一个屏幕工具站点上,这种转变 就是 转化,而正是这段代码触发了它。

例如,它驱动着 blankscreen.io 的黑屏页面 —— 该页面的全部任务是将显示器变成纯黑,点击或按下 F 键进入全屏,隐藏光标,保持唤醒状态只要它在屏幕上显示,并在按下 Esc 时干净利落地退出。如果你在 iPhone 上打开它,你会看到上面的伪全屏回退在发挥作用。

同样的模式在实际中也存在:一次点击或按下 F 键,整个显示器变黑,屏幕保持唤醒状态只要它在显示。按 Esc 键浏览器恢复。

只要掌握了这个模式,将其添加到计时器、幻灯片或 Canvas 实验中,只需放入你想让其填满屏幕的元素即可。

快速陷阱清单

在发布之前,请检查此清单。每一项都是我至少曾经踩过的坑:

  • 需要用户手势: 在加载时、计时器中或在耗时的 await 之后不得进入全屏。

  • 不要自行跟踪状态: 读取 document.fullscreenElement 并监听 fullscreenchange 事件。

  • 无法阻止Esc: 设计时应考虑用户可能随时离开。

  • 旧版 Safari 需要 webkit 前缀 来处理方法 以及 事件名称。

  • iPhone Safari 不支持元素全屏: 使用 document.fullscreenEnabled 进行特性检测,并将运行时的拒绝视为回退触发器,而不仅仅是控制台警告。

  • 将你的快捷键与页面自身的按键进行对比: 如果应用占用了字母键,则让快捷键仅 进入 全屏,退出交给 Esc。

  • 使全屏区域可通过键盘访问(tabindex="0"、Enter/Space)。

  • 唤醒锁需要 HTTPS: 在省电模式下可能被拒绝,页面隐藏时会被撤销。在 visibilitychange 时重新申请。

  • 释放唤醒锁,在不再需要时立即释放。

  • 将背景放置在 html 上,而不仅仅是 body,在页面全屏时。

结论

全屏 API 就是这样一个浏览器功能:乍看之下只是一行代码,但实际上它有自己的个性。

核心(requestFullscreen()、exitFullscreen()、fullscreenElement 以及 fullscreenchange 事件)其实很简单。关键在于遵守用户手势规则,让 UI 诚实地反映真实状态,处理 Safari 的历史记录,并记住:如果屏幕两分钟后变暗,“全屏”仅仅是工作的一半。Wake Lock API 能弥补这一差距,通过 fullscreenchange 将两者配合使用,可使两者保持整洁。

取上面的六十行模式,将你自己的元素放进去,你就能得到一个适用于从演示到游戏的各种场景的生产就绪全屏体验。

编码愉快!

——

🧑‍💻

zhirenhun

一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。