1. 项目概述:一个横屏游戏开发者的“心头刺”
如果你用 Cocos Creator 开发过 H5 游戏,并且目标平台包含 iOS 的 Safari 浏览器,那你一定对下面这个场景不陌生:你精心设计的横屏游戏,在用户将手机旋转到横屏模式后,屏幕顶部那个该死的 Safari 地址栏(以及底部的工具栏)就像一块顽固的膏药,死死地贴在画面上方,生生吃掉了一部分宝贵的游戏可视区域。更让人恼火的是,它时而出现,时而隐藏,行为飘忽不定,直接破坏了游戏的沉浸感。这个问题,几乎成了每一个面向移动端浏览器的 H5 游戏开发者必踩的“大坑”。
我最初遇到这个问题时,也以为只是个简单的“全屏”API调用。但实际深入后才发现,这背后是 iOS Safari 为了平衡用户体验和安全而设计的一套复杂逻辑,远不是一句requestFullscreen就能解决的。特别是对于像 Cocos Creator 这样以 Canvas 作为主要渲染载体、且通常设置为固定尺寸(如 1920x1080)的横屏游戏,我们面对的是一个“静态”画布与一个“动态”的浏览器视口之间的博弈。
简单来说,iOS Safari 自动隐藏地址栏(即进入“全屏”模式)的核心触发机制是“页面滚动”。如果页面内容的高度超过了视口(viewport)的高度,用户一个轻微的上滑动作就能让地址栏优雅地滑走。但我们的横屏游戏呢?为了适配各种屏幕,我们往往将 Canvas 的尺寸通过 CSS 设置为100%宽高,或者通过缩放来填满屏幕。在横屏状态下,游戏画布的高度通常就等于视口的高度,没有“溢出”,没有滚动条。Safari 一看:“哟,这页面高度正好嘛,用户没地方滚,那地址栏我就留着吧。”于是,问题就产生了。
所以,这个“避坑指南”要解决的,远不止一个技术点。它是一场针对特定浏览器行为的前端“外科手术”,涉及对viewportmeta 标签的精准控制、对 CSS 样式的巧妙运用、对 JavaScript 事件(如旋转、触摸)的捕获与响应,以及对 Cocos Creator 引擎本身打包配置的深度调整。目标只有一个:在 iOS Safari 横屏状态下,稳定、可靠地隐藏地址栏和底部工具栏,为玩家提供一个纯净、全屏的游戏画面,同时确保游戏本身的渲染与交互不受任何影响。
2. 核心问题与Safari行为逻辑深度解析
要解决问题,必须先透彻理解“敌人”的行为模式。我们不能与浏览器的设计哲学硬碰硬,而是要巧妙地引导它,让它按照我们期望的方式工作。
2.1 Safari 地址栏的“自动隐藏”与“手动全屏”
首先必须分清两个概念,它们常常被混淆:
自动隐藏(Automatic Hiding):这是 Safari 在移动端的默认体验。当页面可滚动时,用户向下滑动页面,地址栏会向上收起;向上滑动到顶部,地址栏又会出现。这个行为是浏览器内置的,目的是在提供页面信息(地址)和最大化内容区域之间取得平衡。我们的核心目标,就是要在横屏下稳定触发这种“自动隐藏”并使其保持隐藏状态。
手动全屏 API(Fullscreen API):即通过 JavaScript 调用
document.documentElement.requestFullscreen()。这个 API 在桌面端浏览器上可以隐藏所有浏览器UI,但在 iOS Safari 上受到严格限制。对于普通的网页内容,Safari 根本不支持此 API。它只对特定的视频元素或某些特殊的 Web App 模式(如“添加到主屏幕”后以全屏模式打开)提供有限的全屏支持。所以,想用标准 Fullscreen API 来解决我们的问题,此路不通。
2.2 横屏下地址栏不隐藏的根本原因
关键在于“可滚动区域”(scrollable area)。Safari 判断是否隐藏地址栏的简化逻辑如下:
- 条件:页面内容高度 > 视口高度(即存在垂直滚动可能性)。
- 触发:用户发起一个垂直方向的触摸滑动(即使滑动距离很小)。
- 结果:地址栏隐藏。
对于竖屏页面,这很容易满足。但对于一个设计为横屏(例如 16:9)的游戏:
- 我们通常设置
<meta name="viewport" content="width=device-width, initial-scale=1.0">。竖屏时,device-width是屏幕的短边(如 390px),视口较窄,页面内容高度容易超出。 - 当用户旋转到横屏时,
device-width变成了屏幕的长边(如 844px)。如果我们游戏的画布是 1920x1080,经过缩放适配后,其渲染高度很可能恰好等于横屏视口的高度(因为我们要铺满屏幕)。此时,页面内容高度 === 视口高度,垂直滚动量为0。 - Safari 检测不到可滚动区域,因此认为“无需隐藏地址栏”。即使用户尝试滑动,因为没有滚动发生,地址栏也纹丝不动。
2.3 另一种“坑”:动态旋转时的布局抖动
即使你通过一些方法让地址栏在横屏初始状态就隐藏了,另一个棘手问题会出现:当用户从竖屏旋转到横屏时,Safari 的视口尺寸计算会发生突变。地址栏的隐藏/显示会引起可用窗口高度(window.innerHeight)的变化。如果 Cocos Creator 的 Canvas 缩放是依赖window.innerHeight或window.outerHeight计算的,那么旋转过程中,画布可能会经历“缩放 -> 突然变化 -> 再缩放”的过程,导致游戏画面短暂抖动或缩放比例错误,体验极差。
因此,一个完整的解决方案必须同时处理:
- 初始化隐藏:确保页面在横屏加载时,地址栏就是隐藏的。
- 旋转稳定性:在设备方向变化时,保持布局稳定,避免抖动。
- 交互保持:在地址栏隐藏后,确保用户的触摸事件能正确传递给游戏,不会误触发浏览器的下拉刷新等原生行为。
3. 解决方案一:Meta Viewport 与 CSS 的魔法
这是最基础、也是最重要的一步。我们需要通过 HTML 的 meta 标签和 CSS 来“欺骗” Safari,让它认为我们的页面有足够的可滚动空间。
3.1 关键 Meta Viewport 配置
不要使用 Cocos Creator 构建后默认生成的简单 viewport。我们需要一个更强大的配置。在index.html的<head>部分,使用如下配置:
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover">让我们拆解每个参数的作用:
width=device-width:标准设置,让视口宽度等于设备宽度。initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0:这是关键!将缩放比例锁定为1.0,并且禁止用户手动缩放。这能有效防止在旋转或滑动时,Safari 自动调整缩放比例,从而引发不可预料的布局计算和地址栏行为。user-scalable=no:进一步禁用用户缩放,与上面的缩放锁定配合。viewport-fit=cover:至关重要!这个属性告诉浏览器,让视口覆盖整个屏幕区域,包括刘海屏、圆角等“安全区域”之外的部分。它为后续使用 CSSenv(safe-area-inset-*)处理异形屏奠定了基础,同时也更符合全屏游戏的诉求。
注意:在 iOS 15 及之后的版本中,单纯使用
user-scalable=no可能被 Safari 忽略以保障可访问性。但结合maximum-scale=1.0, minimum-scale=1.0双重锁定,在实践中最有效。
3.2 创建“可滚动”的 CSS 结构
既然 Safari 需要可滚动区域,我们就给它一个。但我们要确保这个滚动不会影响游戏主画布的视觉表现。
思路:将整个页面 body 设置为一个可滚动的、高度略大于 100% 的容器,而将游戏 Canvas 绝对定位并固定在顶部。这样,Safari 能检测到滚动,但用户看到的始终是固定的全屏画布。
具体 CSS 方案:
html, body { margin: 0; padding: 0; /* 关键:让 html 和 body 撑满,并允许滚动 */ width: 100%; height: 100%; overflow: hidden; /* 先隐藏,我们用内部容器制造滚动 */ position: relative; background-color: #000; /* 设置游戏背景色,避免滚动时露白 */ } /* 创建一个用于“欺骗”滚动的包装器 */ #scroll-wrapper { position: absolute; top: 0; left: 0; width: 100%; /* 高度设为 101% 或更多,确保产生垂直滚动空间 */ height: calc(100% + 1px); overflow-y: scroll; /* 启用垂直滚动 */ -webkit-overflow-scrolling: touch; /* 启用 iOS 弹性滚动,体验更佳 */ /* 隐藏滚动条 */ scrollbar-width: none; /* Firefox */ -ms-overflow-style: none; /* IE and Edge */ } #scroll-wrapper::-webkit-scrollbar { display: none; /* Chrome, Safari, Opera*/ } /* Cocos Creator 的 Canvas 容器 */ #GameDiv { /* Canvas 容器固定定位,永远覆盖视口 */ position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; /* 处理刘海屏:确保内容在安全区域内 */ padding-top: env(safe-area-inset-top); padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); padding-bottom: env(safe-area-inset-bottom); box-sizing: border-box; /* 禁止任何可能干扰游戏的事件 */ touch-action: none; pointer-events: auto; } /* Canvas 元素本身 */ #gameCanvas { display: block; width: 100%; height: 100%; }对应的 HTML 结构需要调整:
<body> <div id="scroll-wrapper"></div> <!-- 这个 div 是空的,仅用于滚动 --> <div id="GameDiv"> <canvas id="gameCanvas"></canvas> </div> <script src="main.js"></script> </body>原理:#scroll-wrapper的高度是100% + 1px,它相对于body产生了 1 像素的溢出滚动。Safari 检测到了这个可滚动容器。当页面加载或旋转时,我们可以用极少的 JavaScript 代码,让这个包装器向下滚动 1 个像素(scroll-wrapper.scrollTop = 1)。这个微小的滚动动作,就足以触发 Safari 的“用户进行了滚动”的判断,从而自动隐藏地址栏。而#GameDiv是fixed定位的,完全不受这个滚动影响,玩家看到的始终是完美的全屏游戏画面。
实操心得:这个
1px的滚动量是经验值。有时可能需要稍微大一点,比如10px,以确保在所有机型上都能可靠触发。但切忌滚动太多,否则在竖屏模式下可能会看到明显的背景闪动。
4. 解决方案二:JavaScript 的事件驱动与控制
CSS 布局创造了条件,JavaScript 则负责在正确的时机执行触发动作,并处理设备旋转等动态事件。
4.1 页面加载与初始隐藏
我们需要在页面加载后,立即触发一次“滚动”来隐藏地址栏。但要注意执行时机。
(function() { // 等待页面基本元素加载完毕 document.addEventListener('DOMContentLoaded', function() { var scrollWrapper = document.getElementById('scroll-wrapper'); if (scrollWrapper) { // 使用 setTimeout 确保布局已稳定 setTimeout(function() { // 关键:滚动1像素以触发地址栏隐藏 scrollWrapper.scrollTop = 1; // 立即将滚动位置重置回顶部,确保视觉上无偏移 // 注意:有的方案建议不重置,让那1px一直滚着。但重置可以避免极少数情况下的副作用。 // 可以尝试两种方式,选择更稳定的一种。 // scrollWrapper.scrollTop = 0; // 可选 // 强制触发一次重排,确保 Safari “注意到”滚动 void scrollWrapper.offsetHeight; }, 100); // 100ms 延迟是一个比较安全的经验值 } }); })();4.2 处理设备旋转(Orientation Change)
设备旋转是地址栏问题复现的高发区。我们必须监听这个事件,并在旋转完成后重新触发隐藏逻辑。
// 定义一个处理全屏的函数 function ensureFullscreen() { var scrollWrapper = document.getElementById('scroll-wrapper'); if (!scrollWrapper) return; // 在旋转后,Safari 可能有一个短暂的布局计算期 // 太早执行可能无效,需要稍作延迟 setTimeout(function() { // 再次滚动触发隐藏 scrollWrapper.scrollTop = 1; // 可选:重置 scrollTop = 0; void scrollWrapper.offsetHeight; // **重要:修复旋转抖动** // 旋转后,window.innerHeight 会变化。如果Cocos引擎据此调整Canvas,会导致画面缩放。 // 我们需要在旋转完成后,获取稳定的窗口尺寸,并通知Cocos引擎。 if (window.cc && window.cc.view) { // 假设Cocos Creator的适配策略是基于设计分辨率和屏幕宽高比 // 这里需要根据你项目具体的适配方案来调整 // 例如,强制重置引擎的视图尺寸 setTimeout(function() { cc.view.setCanvasSize(window.innerWidth, window.innerHeight); // 或者调用你的游戏分辨率适配函数 // adjustCanvasSize(); }, 200); // 比触发滚动的延迟更长,确保布局完全稳定 } }, 300); // 旋转后300ms执行,这是一个经过大量测试的相对可靠值 } // 监听旋转事件 var orientationChangeTimeout; window.addEventListener('orientationchange', function() { // 清除可能的旧定时器 clearTimeout(orientationChangeTimeout); // 设置一个新的定时器,在旋转事件结束后执行 orientationChangeTimeout = setTimeout(ensureFullscreen, 500); }); // 同时监听 resize 事件作为备用(某些安卓机或特殊情况下) window.addEventListener('resize', function() { clearTimeout(orientationChangeTimeout); orientationChangeTimeout = setTimeout(ensureFullscreen, 500); });4.3 处理触摸事件与防止下拉刷新
在隐藏地址栏后,页面顶部区域(对应原地址栏位置)的触摸事件需要妥善处理,防止触发 Safari 的下拉刷新(在 iOS 13+ 的某些模式下会出现)。
// 在 GameDiv 上禁止默认的触摸行为 document.getElementById('GameDiv').addEventListener('touchmove', function(e) { // 如果游戏需要全局捕获触摸事件,可以在这里阻止默认行为 // 但这可能会影响游戏内部的滚动组件,需要谨慎 // e.preventDefault(); }, { passive: false }); // 必须设置 passive: false 才能 preventDefault // 更精准的做法:只在页面顶部边缘下拉时阻止,防止刷新 var startY = 0; document.getElementById('GameDiv').addEventListener('touchstart', function(e) { startY = e.touches[0].pageY; }, { passive: true }); document.getElementById('GameDiv').addEventListener('touchmove', function(e) { var currentY = e.touches[0].pageY; // 如果当前滚动位置在顶部,且是向下拉 if (window.pageYOffset === 0 && currentY > startY) { // 轻微下拉就阻止,防止触发刷新 if (currentY - startY > 10) { e.preventDefault(); } } // 如果游戏有自己的垂直拖动逻辑,这里的判断需要更复杂 }, { passive: false });5. Cocos Creator 引擎侧的适配与配置
前端的布局和 JS 脚本是“外功”,Cocos Creator 项目本身的配置则是“内功”,必须内外兼修。
5.1 构建发布设置
- 设计分辨率:在
项目设置 -> 项目数据中,设置一个横屏的设计分辨率,例如1920 x 1080。这是你游戏世界的逻辑尺寸。 - 适配策略(Fit Height / Fit Width):这是关键中的关键。在
Canvas组件的适配模式中,选择FIXED_HEIGHT(固定高度)通常是对横屏游戏最友好的。- 原理:
FIXED_HEIGHT会保证游戏画面的高度始终等于设计分辨率的高度(1080px),宽度则按屏幕比例缩放。这样无论在什么屏幕比例下,游戏垂直方向的内容都是完整的,水平方向可能裁剪或留黑边。更重要的是,它保证了游戏渲染内容的高度是固定的逻辑值,不会因为window.innerHeight的瞬时变化(地址栏隐藏导致的)而产生剧烈缩放,从而有效减少旋转抖动。 - 对比:
SHOW_ALL会保证内容全部显示,但可能留黑边;FIXED_WIDTH则可能导致在很高或很矮的屏幕上垂直方向被过度裁剪。FIXED_HEIGHT在稳定性和一致性上通常最优。
- 原理:
- 构建模板:构建 Web Mobile 时,可以自定义
index.html模板。将我们前面提到的viewportmeta 标签和 CSS 结构直接写入模板中,这样就不需要每次构建后手动修改index.html了。模板文件位于 Cocos Creator 安装目录的resources/templates/web-mobile下,可以复制出来修改后,在构建面板的“自定义构建模板”中指定。
5.2 游戏启动脚本中的增强处理
在 Cocos Creator 的游戏启动脚本(通常是main.js或application.js)中,我们需要将前面提到的 JavaScript 控制逻辑集成进去。确保在引擎初始化cc.game.run之前或之后,执行我们的ensureFullscreen函数。
一个常见的做法是,修改 Cocos Creator 生成的main.js,在window.boot函数内部,cc.game.run之前,添加我们的初始化代码:
window.boot = function () { // 1. 我们的全屏初始化代码 function initFullscreenWorkaround() { // ... 此处包含前面章节的 ensureFullscreen 函数、事件监听等所有JS代码 ... // 在DOMContentLoaded或适当时机调用 ensureFullscreen if (document.readyState === 'complete') { ensureFullscreen(); } else { window.addEventListener('load', ensureFullscreen); } } initFullscreenWorkaround(); // 2. 原有的Cocos配置 var settings = window._CCSettings; // ... 其他原有代码 ... // 3. 启动引擎 onStart = function () { cc.loader.downloader._subpackages = settings.subpackages; cc.game.run(settings, onStart); }; // ... 后续代码 ... };5.3 处理安全区域(Safe Area)
对于 iPhone X 及以后的刘海屏、药丸屏设备,在强制全屏后,需要确保游戏的重要 UI 元素(如按钮、血量条)不被刘海或圆角遮挡。这需要用到 CSS 的env(safe-area-inset-*)变量。
我们在前面的 CSS 中已经为#GameDiv添加了padding。但 Cocos Creator 渲染的 Canvas 本身可能不知道这些 padding。我们需要将安全区域信息传递给游戏逻辑。
方法:在 JavaScript 中获取安全区域插入值,并将其作为参数传递给 Cocos 引擎,或者在游戏内设置一个“安全区”节点。
// 在 initFullscreenWorkaround 函数中 function updateSafeArea() { const div = document.getElementById('GameDiv'); // 获取计算后的安全区域值 const style = window.getComputedStyle(div); const top = parseInt(style.paddingTop) || 0; const bottom = parseInt(style.paddingBottom) || 0; const left = parseInt(style.paddingLeft) || 0; const right = parseInt(style.paddingRight) || 0; // 将这些值传递给 Cocos 游戏世界 if (window.cc && window.cc.sys) { // 可以存放到一个全局对象中 window._safeAreaInsets = { top, bottom, left, right }; // 或者通过发射事件通知游戏场景 if (cc.director && cc.director.getScene()) { cc.systemEvent.emit('safe-area-changed', window._safeAreaInsets); } } } // 在 ensureFullscreen 函数中调用 updateSafeArea // 同时在 orientationchange 事件后也需要调用在游戏场景中,监听这个事件,然后调整你的 UI 根节点的位置或边距,确保所有关键 UI 元素都位于top到bottom,left到right的安全区域内。
6. 进阶技巧、测试与疑难排查
即使实现了上述所有步骤,在不同机型、不同 iOS 版本上,仍然可能遇到一些边界情况。这里分享一些进阶技巧和排查方法。
6.1 针对 iOS 15+ 的特殊处理
从 iOS 15 开始,Safari 的地址栏 UI 和行为有了一些变化。有时地址栏会以更紧凑的形式出现,隐藏逻辑也更“固执”。除了之前提到的强力 viewport 设置,还可以尝试:
- 在
scroll-wrapper的滚动事件中持续干预:document.getElementById('scroll-wrapper').addEventListener('scroll', function(e) { // 如果地址栏又出现了(表现为 scrollTop 被重置为0),就再次把它滚下去 if (this.scrollTop === 0) { this.scrollTop = 1; } }); - 使用
scrollTo代替直接赋值scrollTop:有些开发者反馈element.scrollTo(0, 1)比element.scrollTop = 1更可靠。
6.2 真机调试与问题排查清单
在电脑模拟器上很难完全复现 Safari 的行为,真机调试是必须的。
如何调试:
- 将构建后的游戏部署到一台 HTTPS 服务器上(iOS Safari 对许多 API 要求 HTTPS)。
- 在 iPhone 的 Safari 中打开游戏页面。
- 用 USB 连接 iPhone 到 Mac。
- 在 Mac 的 Safari 浏览器中,打开“开发”菜单,选择你的 iPhone 设备,然后选择正在运行的网页。即可打开 Safari 开发者工具,进行断点、查看元素、日志输出等操作。
排查清单:
- 地址栏完全不隐藏:检查
viewportmeta 标签是否正确设置,特别是缩放是否被锁定。检查#scroll-wrapper的高度是否确实大于其父容器,并且overflow-y是scroll或auto。检查 JavaScript 中的scrollTop赋值是否成功执行(在控制台输出scrollWrapper.scrollTop查看)。 - 旋转时画面剧烈抖动/闪烁:这通常是 Cocos Canvas 在旋转过程中被多次重设尺寸导致的。确保你的
orientationchange处理函数中有足够的延迟(300ms+),并且只调用一次引擎的尺寸调整函数。使用FIXED_HEIGHT适配策略能极大缓解此问题。 - 游戏触摸事件失灵:检查是否为
#GameDiv或#gameCanvas添加了touch-action: none或pointer-events: none等样式,或者是否在touchmove事件中错误地调用了e.preventDefault()阻止了事件冒泡。确保游戏需要的触摸事件能正常传递到 Cocos 的输入系统。 - 底部工具栏(导航栏)不隐藏:底部工具栏的隐藏逻辑与地址栏类似,但有时更顽固。确保你的页面内容在垂直方向上有足够的“可滚动”空间。有时需要将
#scroll-wrapper的高度设得更大(如height: calc(100% + 100px)),并初始滚动相应的像素(scrollTop = 60)。
- 地址栏完全不隐藏:检查
6.3 备选方案:Web App 全屏模式(添加到主屏幕)
如果上述所有 Hack 方法在你目标用户的 iOS 版本上仍然不稳定,还有一个“终极”方案:引导用户将你的游戏**“添加到主屏幕”**。
当用户通过主屏幕图标启动网页时,Safari 会以一种特殊的“无浏览器UI”模式(Standalone Mode)运行。在这个模式下,地址栏和底部工具栏默认就是隐藏的,体验接近原生 App。
实现方法:
- 在
index.html的<head>中添加 Web App Capable 的 meta 标签:<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> <!-- 或 black --> - 提供合适的图标(
apple-touch-icon)。 - 在游戏内通过 UI 文字或弹窗,友好地提示用户“为获得最佳体验,请点击分享按钮,选择‘添加到主屏幕’”。
这个方案的缺点是依赖用户操作,无法在普通浏览器访问时自动生效。但对于追求极致体验的核心用户,是一个非常好的补充方案。
7. 完整代码整合与最佳实践建议
将上述所有碎片整合成一个可用的方案,需要一定的工程化思维。以下是一个简化的整合思路和最佳实践建议。
项目结构建议:
- 自定义构建模板:修改 Cocos Creator 的 Web Mobile 构建模板,将优化后的
index.html(包含 viewport 和基础 CSS 结构)固定下来。 - 独立的全屏工具脚本:创建一个单独的 JavaScript 文件,例如
fullscreen-helper.js,包含所有的事件监听、滚动控制、安全区计算逻辑。这个脚本应该尽量轻量,与游戏业务逻辑解耦。 - 在 main.js 中引入:在 Cocos Creator 的启动入口
main.js中,最早可能的地方引入并执行fullscreen-helper.js的初始化函数。 - 游戏内的安全区适配:在游戏启动场景中,监听安全区域变化事件,并调整 UI 布局。可以将安全区信息保存到全局管理器,供所有 UI 模块查询。
最佳实践总结:
- 优先使用 CSS + 微小滚动 Hack:这是目前兼容性最广、相对最稳定的方案。
- 锁定 Viewport 缩放:
maximum-scale=1.0, minimum-scale=1.0是稳定性的基石。 - 采用 FIXED_HEIGHT 适配:在 Cocos Creator 中,这能最大程度减少旋转抖动。
- 延迟执行是关键:在
load、orientationchange事件后,使用setTimeout给予浏览器足够的布局计算时间,再执行滚动触发。 - 真机、真机、真机:重要的事情说三遍,必须在不同型号的 iOS 设备上进行测试。
- 提供降级体验:如果你的游戏 UI 在地址栏显示时会被遮挡,考虑设计一个动态的 UI 布局,当检测到地址栏未隐藏时,自动将关键 UI 元素下移,虽然不完美,但比被挡住要好。可以通过定期检查
window.innerHeight与window.outerHeight或screen.height的差值来推测地址栏是否可见。
解决 iOS Safari 横屏全屏问题没有一劳永逸的银弹,它更像是一场与特定浏览器行为的持续“磨合”。上面提供的方案融合了社区众多开发者的智慧,在大多数情况下能取得很好的效果。但技术总是在变化,iOS 和 Safari 也在不断更新。最根本的,是理解其底层逻辑——利用可滚动区域触发原生隐藏行为。只要抓住这个核心,无论浏览器如何变化,你都能找到相应的应对策略。在实际项目中,我通常会准备一个“全屏兼容性测试页”,快速验证新想法在不同设备上的效果,这比在完整游戏项目中调试要高效得多。希望这份详细的指南,能帮你彻底填上这个“坑”,让玩家的第一眼体验再无瑕疵。