1. 项目概述:为什么我们需要全屏显示?
做前端开发或者网页设计的朋友,肯定都遇到过这样的场景:你精心制作了一个数据可视化大屏,希望用户能沉浸式地查看图表;或者你开发了一个在线演示工具,需要像PPT一样全屏播放;又或者是一个简单的图片画廊,全屏查看才能获得最佳的视觉体验。在这些时候,浏览器默认的地址栏、标签页、书签栏等界面元素就成了“碍眼”的存在,它们不仅占据了宝贵的屏幕空间,还破坏了内容的整体感和沉浸感。
过去,要实现类似效果,我们可能需要依赖Flash或者一些浏览器的“伪全屏”模式,体验并不完美,而且兼容性是个大问题。现在,HTML5为我们带来了一个原生、强大且标准化的解决方案——Fullscreen API。这个API允许网页中的特定元素(不仅仅是整个文档)以真正的全屏模式显示,完全接管用户的屏幕,隐藏所有浏览器UI,让内容成为绝对的主角。
这不仅仅是“把窗口变大”那么简单。Fullscreen API提供了一套完整的事件机制,让开发者可以精确地知道全屏状态何时开始、何时结束,并据此调整页面的交互逻辑。无论是制作一个全屏的视频播放器,还是一个需要专注模式的在线代码编辑器,这个API都是实现沉浸式体验的关键技术。接下来,我就结合自己多年的踩坑经验,带你从零开始,彻底搞懂这个看似简单实则暗藏玄机的API。
2. 核心需求与API设计解析
2.1 全屏模式的核心诉求
在深入代码之前,我们先明确一下,一个合格的全屏功能应该满足哪些核心诉求:
- 目标元素灵活:用户可能希望整个网页全屏,也可能只希望网页中的某个
<div>、<video>或<canvas>元素全屏。API必须支持指定任意DOM元素。 - 状态可控:程序需要能主动触发进入全屏,也需要能主动退出全屏。更重要的是,需要能监听到全屏状态的变化(例如用户按了ESC键退出)。
- 样式自适应:元素进入全屏后,其CSS样式环境可能会发生变化(比如
:fullscreen伪类),我们需要能针对全屏状态编写特定的样式,以优化布局。 - 用户体验友好:全屏请求必须由用户手势(如点击)触发,这是浏览器的安全策略,防止网页恶意劫持屏幕。退出方式对用户来说也应该是明确且符合直觉的。
- 浏览器兼容性:虽然是一个标准API,但不同浏览器在实现时仍有前缀差异,我们的代码需要优雅地处理这些兼容性问题。
Fullscreen API正是围绕这些诉求设计的。它的核心方法非常简洁:requestFullscreen()用于请求进入全屏,exitFullscreen()用于退出全屏。而事件fullscreenchange和fullscreenerror则构成了状态监听的核心。
2.2 方法、属性与事件详解
核心方法:
Element.requestFullscreen(options): 请求使某个元素进入全屏模式。这里的options是一个可选对象,在现代浏览器中可以用来设置导航UI的显示行为(例如{ navigationUI: “hide” }),但支持度有限,大多数情况下我们使用默认参数即可。Document.exitFullscreen(): 请求退出全屏模式。注意,这个方法是在document对象上调用,而不是在元素上。
关键属性:
Document.fullscreenElement: 这是一个只读属性,返回当前处于全屏模式的DOM元素。如果当前没有元素在全屏状态,则返回null。这是我们判断当前是否处于全屏状态以及是哪个元素在全屏的核心依据。
重要事件:
fullscreenchange: 当全屏状态发生变化时(进入或退出)触发。这个事件在document对象上监听。fullscreenerror: 当全屏请求因某些原因失败时触发。例如,如果请求不是由用户手势触发,或者iframe没有设置allowfullscreen属性,就可能触发此事件。
注意:
fullscreenchange事件不会告诉你变化的方向(是进入还是退出)。你必须通过检查document.fullscreenElement属性来判断当前的实际状态。这是一个非常常见的困惑点。
3. 从零实现一个健壮的全屏控制器
理论说完了,我们直接上代码,打造一个可以应对各种情况的通用全屏工具函数。我会把兼容性处理和最佳实践都融入其中。
3.1 基础实现:请求与退出
首先,我们封装兼容不同浏览器前缀的通用方法。这是处理Web API历史遗留问题的标准操作。
/** * 获取带有正确前缀的Fullscreen API方法、属性和事件名 * 用于处理不同浏览器的兼容性 */ function getFullscreenAPI() { const doc = document; const api = { requestFn: null, exitFn: null, elementProp: null, changeEvent: null, errorEvent: null, }; // 尝试匹配标准API及各种浏览器前缀 if (doc.exitFullscreen) { // 标准API (Chrome, Firefox, Edge新版, Opera) api.requestFn = 'requestFullscreen'; api.exitFn = 'exitFullscreen'; api.elementProp = 'fullscreenElement'; api.changeEvent = 'fullscreenchange'; api.errorEvent = 'fullscreenerror'; } else if (doc.webkitExitFullscreen) { // 带webkit前缀的API (Safari, 旧版Chrome/Edge) api.requestFn = 'webkitRequestFullscreen'; api.exitFn = 'webkitExitFullscreen'; api.elementProp = 'webkitFullscreenElement'; api.changeEvent = 'webkitfullscreenchange'; api.errorEvent = 'webkitfullscreenerror'; } else if (doc.msExitFullscreen) { // 带ms前缀的API (旧版IE) api.requestFn = 'msRequestFullscreen'; api.exitFn = 'msExitFullscreen'; api.elementProp = 'msFullscreenElement'; api.changeEvent = 'MSFullscreenChange'; api.errorEvent = 'MSFullscreenError'; } else if (doc.mozCancelFullScreen) { // 带moz前缀的API (旧版Firefox) api.requestFn = 'mozRequestFullScreen'; // 注意Firefox中是RequestFullScreen api.exitFn = 'mozCancelFullScreen'; // 注意Firefox中是CancelFullScreen api.elementProp = 'mozFullScreenElement'; api.changeEvent = 'mozfullscreenchange'; api.errorEvent = 'mozfullscreenerror'; } return api; } // 使用封装的API const fsApi = getFullscreenAPI(); /** * 请求进入全屏模式 * @param {HTMLElement} element - 需要全屏显示的元素,默认为整个document.body * @returns {Promise} - 返回一个Promise,在全屏成功时resolve,失败时reject */ function requestFullscreen(element = document.body) { if (!fsApi.requestFn) { return Promise.reject(new Error('您的浏览器不支持Fullscreen API')); } // 关键:全屏请求必须由用户手势(如click)触发,在异步操作(如setTimeout, Promise, 事件回调)中调用是允许的。 try { // 现代浏览器中,requestFullscreen返回一个Promise const requestPromise = element[fsApi.requestFn](); if (requestPromise instanceof Promise) { return requestPromise.catch(err => { // 将错误统一抛出 throw new Error(`全屏请求被拒绝: ${err.message}`); }); } // 对于不支持返回Promise的旧浏览器,我们返回一个自定义的resolve的Promise return Promise.resolve(); } catch (err) { return Promise.reject(new Error(`调用全屏方法出错: ${err.message}`)); } } /** * 退出全屏模式 * @returns {Promise} - 返回一个Promise,在退出成功时resolve */ function exitFullscreen() { if (!fsApi.exitFn) { return Promise.reject(new Error('您的浏览器不支持Fullscreen API')); } const doc = document; try { // exitFullscreen也返回Promise const exitPromise = doc[fsApi.exitFn](); if (exitPromise instanceof Promise) { return exitPromise; } return Promise.resolve(); } catch (err) { return Promise.reject(new Error(`退出全屏出错: ${err.message}`)); } } /** * 切换全屏状态 * @param {HTMLElement} element - 要切换全屏状态的元素 * @returns {Promise} */ function toggleFullscreen(element = document.body) { const doc = document; const fsApi = getFullscreenAPI(); const currentElement = doc[fsApi.elementProp]; if (currentElement) { // 如果已有元素在全屏,则退出 return exitFullscreen(); } else { // 否则,请求指定元素全屏 return requestFullscreen(element); } } /** * 判断当前是否处于全屏状态 * @returns {boolean} */ function isFullscreen() { const fsApi = getFullscreenAPI(); return !!document[fsApi.elementProp]; } /** * 获取当前全屏的元素 * @returns {HTMLElement|null} */ function getFullscreenElement() { const fsApi = getFullscreenAPI(); return document[fsApi.elementProp]; }为什么这么封装?
- 兼容性处理:
getFullscreenAPI函数一次性检测并返回当前浏览器环境下的正确API名称,避免了在代码中到处写if...else。 - Promise化:现代浏览器的
requestFullscreen和exitFullscreen方法返回Promise,这让我们可以用.then().catch()的链式调用处理成功和失败,代码更清晰。对于旧浏览器,我们手动包装一个Promise以保持接口一致。 - 集中管理:所有全屏相关逻辑放在一起,易于维护和复用。
3.2 监听全屏状态变化
有了基础方法,我们还需要监听状态变化来更新UI(比如切换一个“全屏”按钮的图标)。
/** * 添加全屏状态变化监听器 * @param {Function} callback - 状态变化时的回调函数,接收一个布尔参数(是否全屏)和当前全屏元素 */ function addFullscreenChangeListener(callback) { const fsApi = getFullscreenAPI(); if (!fsApi.changeEvent) { console.warn('无法监听全屏变化事件,浏览器可能不支持。'); return; } const handler = () => { const isFs = isFullscreen(); const element = getFullscreenElement(); callback(isFs, element); }; document.addEventListener(fsApi.changeEvent, handler); // 返回一个移除监听器的函数,便于清理 return () => { document.removeEventListener(fsApi.changeEvent, handler); }; } // 使用示例 const removeListener = addFullscreenChangeListener((isFullscreen, element) => { const button = document.getElementById('fullscreen-btn'); if (button) { button.textContent = isFullscreen ? '退出全屏' : '进入全屏'; button.dataset.target = isFullscreen ? 'exit' : 'enter'; } console.log(`全屏状态: ${isFullscreen ? '开启' : '关闭'}`, element); }); // 在组件卸载或页面销毁时,记得移除监听器 // removeListener();实操心得:一定要记得管理事件监听器的生命周期。特别是在单页应用(SPA)或动态加载的组件中,如果添加了全局监听器而不移除,会导致内存泄漏和重复执行回调。上面返回一个清理函数是很好的模式。
3.3 处理全屏CSS样式
当元素进入全屏后,浏览器会为其应用一个特殊的伪类:fullscreen。我们可以利用这个伪类为全屏状态下的元素编写特定样式,这是优化全屏体验的关键。
/* 为准备全屏的元素设置基础样式 */ .video-container { width: 80%; margin: 0 auto; transition: background-color 0.3s ease; } /* 当.video-container元素处于全屏状态时的样式 */ .video-container:fullscreen { width: 100%; background-color: #000; /* 全屏时背景设为黑色,更适合视频观看 */ display: flex; justify-content: center; align-items: center; } /* 兼容带前缀的伪类 */ .video-container:-webkit-full-screen { width: 100%; background-color: #000; display: flex; justify-content: center; align-items: center; } .video-container:-moz-full-screen { width: 100%; background-color: #000; display: flex; justify-content: center; align-items: center; } .video-container:-ms-fullscreen { width: 100%; background-color: #000; display: flex; justify-content: center; align-items: center; } /* 全屏时,隐藏页面其他不必要的元素 */ body:fullscreen .header, body:fullscreen .sidebar { display: none; }提示:编写全屏样式时,一个常见的技巧是使用
display: flex; justify-content: center; align-items: center;来确保全屏元素的内容在屏幕中完美居中,这对于展示图片、视频或单个图表非常有用。
4. 实战应用:构建一个全屏图片画廊
现在,我们把上面的知识组合起来,做一个完整的例子——一个支持全屏查看的简易图片画廊。
4.1 HTML结构
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>全屏图片画廊演示</title> <link rel="stylesheet" href="style.css"> </head> <body> <header> <h1>我的旅行相册</h1> <button id="toggle-fs-page" class="fs-btn">📺 全屏浏览</button> </header> <main class="gallery-container"> <div class="image-list"> <div class="image-item"> <img src="https://picsum.photos/400/300?random=1" alt="风景图1" loading="lazy"> <button class="fs-btn">* { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: sans-serif; line-height: 1.6; padding: 20px; background-color: #f5f5f5; transition: background-color 0.3s; } /* 当整个body全屏时的样式 */ body:fullscreen { background-color: #000; padding: 0; display: flex; flex-direction: column; } body:-webkit-full-screen { /* 兼容Safari/Chrome旧版 */ background-color: #000; padding: 0; display: flex; flex-direction: column; } header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px; padding-bottom: 15px; border-bottom: 2px solid #eee; } /* 全屏时隐藏header */ body:fullscreen header { display: none; } .fs-btn { padding: 10px 20px; background-color: #4a6fa5; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 16px; transition: background-color 0.2s; } .fs-btn:hover { background-color: #385d8a; } .fs-btn.exit { background-color: #d9534f; } .fs-btn.exit:hover { background-color: #c9302c; } .gallery-container { max-width: 1200px; margin: 0 auto; } .image-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 25px; } .image-item { background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 12px rgba(0,0,0,0.1); transition: transform 0.3s, box-shadow 0.3s; display: flex; flex-direction: column; } .image-item:hover { transform: translateY(-5px); box-shadow: 0 8px 20px rgba(0,0,0,0.15); } .image-item img { width: 100%; height: 250px; object-fit: cover; display: block; } .image-item .fs-btn { margin: 15px auto; width: 80%; } /* 图片在全屏状态下的样式 */ .image-item img:fullscreen { object-fit: contain; /* 保持比例,完整显示图片 */ background-color: #000; width: 100vw; height: 100vh; } /* 同样需要写带前缀的版本 */ .image-item img:-webkit-full-screen { object-fit: contain; background-color: #000; width: 100vw; height: 100vh; } /* 全屏遮罩层 */ .fullscreen-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.7); color: white; display: none; /* 默认隐藏 */ justify-content: center; align-items: flex-end; /* 内容靠底部显示 */ padding-bottom: 50px; z-index: 9999; } .fullscreen-overlay.active { display: flex; } .overlay-content { text-align: center; background: rgba(0,0,0,0.8); padding: 20px 40px; border-radius: 10px; } .overlay-content kbd { background-color: #333; padding: 2px 8px; border-radius: 4px; font-family: monospace; border: 1px solid #666; }4.3 JavaScript逻辑 (app.js)
// DOM加载完成后执行 document.addEventListener('DOMContentLoaded', function() { // 1. 页面全屏按钮(控制整个body) const pageFsBtn = document.getElementById('toggle-fs-page'); if (pageFsBtn) { pageFsBtn.addEventListener('click', () => { toggleFullscreen(document.body).catch(err => { console.error('页面全屏切换失败:', err); alert('无法切换到全屏模式,请确保操作由点击触发。'); }); }); } // 2. 为每个图片的“全屏查看”按钮绑定事件 const imageFsBtns = document.querySelectorAll('.image-item .fs-btn[data-fs-target="image"]'); imageFsBtns.forEach(btn => { btn.addEventListener('click', function(event) { // 找到按钮所在的.image-item容器内的img元素 const imageItem = this.closest('.image-item'); const imgElement = imageItem.querySelector('img'); if (imgElement) { requestFullscreen(imgElement).catch(err => { console.error('图片全屏失败:', err); alert('图片全屏请求被拒绝。'); }); } event.stopPropagation(); // 防止事件冒泡 }); }); // 3. 遮罩层退出按钮 const exitOverlayBtn = document.getElementById('exit-fs-overlay'); const overlay = document.getElementById('fullscreen-overlay'); if (exitOverlayBtn && overlay) { exitOverlayBtn.addEventListener('click', () => { exitFullscreen().catch(err => { console.error('退出全屏失败:', err); }); }); } // 4. 监听全屏状态变化,控制遮罩层显示/隐藏 const removeChangeListener = addFullscreenChangeListener((isFs, element) => { console.log('全屏状态变化:', isFs, element); // 更新页面全屏按钮文字 if (pageFsBtn) { pageFsBtn.textContent = isFs ? '退出页面全屏' : '📺 全屏浏览'; } // 控制遮罩层:只有当整个body全屏时才显示 if (overlay) { if (isFs && element === document.body) { overlay.classList.add('active'); } else { overlay.classList.remove('active'); } } // 一个额外的技巧:全屏时,监听键盘ESC键(虽然浏览器默认支持,但我们可以添加额外逻辑) if (isFs) { const handleEscKey = (event) => { if (event.key === 'Escape') { console.log('ESC键被按下,准备退出全屏'); // 可以在这里添加退出前的确认逻辑,例如“确定要退出吗?” // exitFullscreen(); } }; document.addEventListener('keydown', handleEscKey); // 我们需要一个方式来移除这个监听器,避免重复添加。一个简单的办法是存起来。 // 为了简化,这里仅作演示。在实际项目中,需要更严谨的事件管理。 } }); // 5. 监听全屏错误事件(可选,用于调试) const fsApi = getFullscreenAPI(); if (fsApi.errorEvent) { document.addEventListener(fsApi.errorEvent, (event) => { console.error('全屏错误事件触发:', event); // 可以根据错误类型给用户更具体的提示 alert('全屏操作发生错误,可能由于浏览器策略或用户取消。'); }); } // 注意:在实际的单页应用中,记得在组件卸载时调用 removeChangeListener() 来清理。 });5. 深入排查:你可能遇到的坑与解决方案
即使按照最佳实践编写代码,在全屏开发中还是会遇到一些棘手的问题。下面是我总结的常见“坑”及其填平方法。
5.1 安全策略限制:用户手势要求
问题现象:代码在按钮的click事件回调中直接调用requestFullscreen(),在本地测试一切正常,但放到线上服务器后,或者在setTimeout、Promise.resolve().then()等异步任务中调用时,全屏请求被拒绝,控制台可能看到类似“API can only be initiated by a user gesture”的错误。
根本原因:这是浏览器最重要的安全策略之一。为了防止网页在用户不知情的情况下恶意进入全屏(类似弹窗广告),规范要求全屏请求必须直接由用户手势事件(如click、touchstart、keydown等)触发。
解决方案:
- 确保直接绑定:将
requestFullscreen()的调用放在用户事件(如click)的事件处理函数内部。 - 避免异步延迟:不要在
setTimeout、setInterval、fetch().then()、Promise.resolve().then()等异步回调中直接调用。如果必须在异步后调用,需要确保用户手势事件是“最近”发生的,但这并不可靠,不同浏览器策略不同。 - 使用
Promise链:现代浏览器的requestFullscreen()返回一个Promise。你可以在用户事件中启动这个Promise,但后续的.then()回调中不能再触发新的全屏请求。
// ✅ 正确:在click事件处理函数中同步调用 fullscreenButton.addEventListener('click', function(event) { // 这是同步执行的,直接由用户点击触发 videoElement.requestFullscreen().catch(handleError); }); // ❌ 错误:在异步回调中调用 fullscreenButton.addEventListener('click', function(event) { setTimeout(() => { // 这已经脱离了原始的用户手势上下文,请求会被拒绝 videoElement.requestFullscreen().catch(handleError); }, 1000); }); // ⚠️ 有风险:即使在Promise.then中,也可能被某些浏览器拒绝 fullscreenButton.addEventListener('click', function(event) { // 先做一些异步操作... fetch('/api/data').then(response => { // 这个.then回调可能不再被认为是由用户手势直接触发的 return videoElement.requestFullscreen(); // 可能失败 }).catch(handleError); });5.2 iframe中的全屏问题
问题现象:你的网页通过<iframe>嵌入,或者你试图让<iframe>内部的内容全屏,但全屏请求失败。
原因与方案:
- iframe需要
allowfullscreen属性:这是最基本的要求。如果iframe没有设置allowfullscreen或allow="fullscreen",其内部文档将无法请求全屏。<!-- 传统属性 --> <iframe src="content.html" allowfullscreen></iframe> <!-- 或使用更现代的allow属性 --> <iframe src="content.html" allow="fullscreen"></iframe> - 跨域iframe限制:如果iframe加载的是跨域内容,即使设置了
allowfullscreen,其内部调用requestFullscreen()也可能受到更严格的限制。这涉及到浏览器的同源策略。通常,跨域iframe的全屏行为是受限的。 - 全屏元素归属:在iframe内部调用全屏,全屏的是iframe内部的元素,还是外层的iframe本身?这取决于实现。标准行为是让iframe内部的元素全屏。但为了最佳兼容性,建议在iframe内部实现独立的全屏控制逻辑。
5.3 样式失效与:fullscreen伪类不生效
问题现象:你为元素编写了:fullscreen的CSS样式,但进入全屏后样式没有应用。
排查步骤:
- 检查CSS选择器优先级:全屏伪类的特异性可能被其他CSS规则覆盖。使用浏览器开发者工具的“元素检查”功能,查看全屏状态下元素实际应用的样式,并检查你的
:fullscreen规则是否被划掉。 - 别忘了浏览器前缀:这是最常见的原因!你必须为
:-webkit-full-screen、:-moz-full-screen、:-ms-fullscreen也编写相同的规则。可以使用CSS预处理器(如Sass)的mixin来简化。// Sass Mixin示例 @mixin fullscreen-styles { &:fullscreen { @content; } &:-webkit-full-screen { @content; } &:-moz-full-screen { @content; } &:-ms-fullscreen { @content; } } .my-element { width: 50%; @include fullscreen-styles { width: 100%; background: black; } } - 检查元素是否真的是全屏元素:通过
document.fullscreenElement确认当前全屏的元素是否是你期望的那个。如果你给.container写了样式,但全屏的是它内部的img,那么.container:fullscreen的样式自然不会生效。
5.4 移动端浏览器兼容性与行为差异
问题现象:在桌面浏览器运行良好的全屏功能,在手机或平板上表现异常,比如无法真正隐藏浏览器地址栏,或者全屏后布局错乱。
经验与应对:
- “真全屏”与“沉浸式”:许多移动端浏览器(特别是iOS Safari和部分安卓浏览器)出于用户体验考虑,并不提供与桌面端完全一致的全屏API。它们可能只是隐藏工具栏,但状态栏(显示时间、电量的那一栏)依然可见。这通常被称为“沉浸式模式”。
- 视口(Viewport)元标签:确保你的HTML头部有正确的视口设置,这对移动端布局至关重要。
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">viewport-fit=cover对于处理有“刘海”或圆角的设备特别有用,它允许网页内容延伸到整个屏幕。 - CSS的
env()函数:为了适配刘海屏,可以使用安全区域(safe area)插入。body { padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom); padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); } - 备选方案:对于移动端,有时需要结合使用Fullscreen API和针对特定浏览器的hack或专有API(虽然不推荐),或者干脆为移动端设计一个不同的“最大化”体验,而不是追求绝对的全屏。
5.5 全屏状态下的键盘导航与焦点管理
问题场景:你开发了一个全屏的幻灯片应用,用户需要按左右箭头键切换幻灯片。但在全屏后,键盘事件似乎没反应了。
问题根源:全屏后,键盘事件的目标可能发生了变化,或者某些键(如方向键、空格键)被浏览器默认行为拦截(例如,在Chrome中,全屏时按空格键可能会触发播放/暂停,如果全屏元素是video的话)。
解决方案:
- 在全屏元素上监听键盘事件:确保为
document或document.documentElement添加keydown事件监听器,并在事件处理函数中调用event.preventDefault()来阻止浏览器的默认行为。function handleFullscreenKeydown(event) { if (!isFullscreen()) return; switch(event.key) { case 'ArrowLeft': event.preventDefault(); // 阻止可能的默认滚动行为 goToPreviousSlide(); break; case 'ArrowRight': event.preventDefault(); goToNextSlide(); break; case 'Escape': // 注意:通常不需要处理ESC,浏览器默认会退出全屏 // 如果你要拦截,可以在这里调用 event.preventDefault() // 但强烈不建议,这违反了用户预期。 break; } } document.addEventListener('keydown', handleFullscreenKeydown); - 管理焦点:如果全屏应用中有可交互元素(如按钮、输入框),确保在全屏切换时,焦点被正确地设置或管理,以保障键盘无障碍访问。
6. 性能优化与高级技巧
当全屏应用变得复杂时(例如全屏3D游戏、数据可视化),这些技巧能帮你提升体验。
6.1 使用requestFullscreen的options参数
现代浏览器(Chrome 71+, Firefox 64+)的requestFullscreen()方法支持一个可选的配置对象。目前唯一广泛支持的选项是navigationUI。
// 请求全屏,并尝试隐藏导航UI(如果浏览器支持) element.requestFullscreen({ navigationUI: "hide" }).then(() => { console.log('全屏成功,并尝试隐藏导航UI'); }).catch(err => { console.log('全屏失败或不支持hide选项:', err); });navigationUI可以设置为:
"auto"(默认): 由浏览器决定是否显示导航UI(如退出全屏的提示)。"hide": 请求浏览器隐藏导航UI。"show": 请求浏览器显示导航UI。
注意:这个选项只是一个提示,浏览器可以忽略它。特别是移动端浏览器,出于安全考虑,通常会始终显示某种形式的退出提示。
6.2 全屏与Canvas/WebGL性能
对于游戏或图形密集型应用,全屏模式能带来显著的性能提升,因为它通常能解锁更高的刷新率(如144Hz)并允许更直接的渲染路径。
const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); // 或 'webgl' // 进入全屏时,可以调整Canvas分辨率以匹配屏幕物理像素,获得更清晰画面 function enterFullscreenForGame() { requestFullscreen(canvas).then(() => { // 全屏后,将canvas的CSS宽高设置为100vw/vh canvas.style.width = '100vw'; canvas.style.height = '100vh'; // 更重要的是:将canvas的drawingBuffer尺寸(即实际像素数)设置为设备像素比缩放后的尺寸 const dpr = window.devicePixelRatio || 1; canvas.width = window.innerWidth * dpr; canvas.height = window.innerHeight * dpr; // 对于2D Canvas,需要缩放上下文以匹配新的像素尺寸 if (ctx instanceof CanvasRenderingContext2D) { ctx.scale(dpr, dpr); } // 对于WebGL,通常需要调用gl.viewport来更新视口 // gl.viewport(0, 0, canvas.width, canvas.height); console.log(`Canvas分辨率已调整为: ${canvas.width} x ${canvas.height}`); }); }6.3 监听分辨率变化
当用户在全屏和非全屏之间切换,或者旋转移动设备时,屏幕分辨率可能发生变化。你需要监听resize事件来调整布局。
// 一个防抖的resize处理函数,避免频繁调整 let resizeTimeout; function handleResize() { // 清除之前的定时器 clearTimeout(resizeTimeout); // 设置新的定时器,延迟执行调整逻辑 resizeTimeout = setTimeout(() => { if (isFullscreen()) { const fsElement = getFullscreenElement(); console.log(`全屏状态下窗口大小改变: ${window.innerWidth}x${window.innerHeight}`); // 在这里更新Canvas尺寸、图表布局等 if (fsElement.tagName === 'CANVAS') { const dpr = window.devicePixelRatio || 1; fsElement.width = window.innerWidth * dpr; fsElement.height = window.innerHeight * dpr; // ... 更新渲染上下文 } } }, 250); // 延迟250毫秒执行 } // 监听窗口大小变化 window.addEventListener('resize', handleResize); // 对于移动端方向变化,也可以监听orientationchange window.addEventListener('orientationchange', handleResize);全屏API是一个强大但细节丰富的工具。从简单的按钮控制到复杂的沉浸式应用,理解其原理、兼容性陷阱和最佳实践,能让你在项目中游刃有余地创造出令人印象深刻的用户体验。最关键的是,始终以用户为中心,确保全屏模式的进入和退出是清晰、可控且符合直觉的。