iOS Safari中fixed遮罩“漏底”的根本原因是vh单位将地址栏高度计入视口,而fixed元素不响应地址栏收起/展开;可靠解法是JS动态设置height=window.innerHeight并锁定body滚动。

为什么 position: fixed 遮罩在 iOS 上经常“漏底”
因为 Safari(尤其是旧版 iOS)对 fixed 元素的视口计算依赖于当前滚动位置和页面缩放状态,一旦页面可滚动或触发了“弹性滚动”,fixed 元素可能不再覆盖整个可视窗口,而是相对于某个祖先容器定位,甚至被截断。
- 常见错误现象:
height: 100vh在 iOS Safari 中实际高度小于屏幕,遮罩下方露出内容;横屏切回竖屏后遮罩高度错乱 - 根本原因:
vh单位在 iOS Safari 中会把地址栏/工具栏高度算进视口,但fixed元素渲染时又不实时响应地址栏收起/展开 - 兼容性影响:iOS 15+ 有所改善,但 iOS 14 及更早版本、部分微信内置浏览器(基于旧版 WKWebView)仍严重存在
用 100svh 替代 100vh 是最简解法吗
不是。虽然 svh(small viewport height)是 CSS Viewport Units Level 2 的新单位,理论上能避开地址栏干扰,但它目前在 Safari 中支持度极差(iOS 16.4+ 才开始实验性支持,且需开启 viewport-fit=cover),生产环境不可靠。
- 使用场景:仅适用于明确只跑在 iOS 17+/macOS Sonoma+ 且可控制 WebView 配置的新应用内嵌页
- 参数差异:
100svh指“用户当前可见区域高度”,100dvh(dynamic viewport height)更理想,但 Safari 完全不支持 - 实操建议:别用
svh做主方案,它现在就像个半成品开关,开了也容易关不上
真正可靠的全屏遮罩写法(含防滚动穿透)
核心思路:放弃纯 CSS 高度声明,用 JS 动态读取 window.innerHeight 并写入内联样式,同时锁定 body 滚动。
- 必须设置
top: 0; left: 0; width: 100%; height: 100%—— 不用vh,也不依赖fixed的“自动填满”幻想 - 遮罩元素需加
position: fixed,但高度由 JS 控制:element.style.height = window.innerHeight + 'px' - 防滚动穿透要双管齐下:
document.body.style.overflow = 'hidden'+touch-action: none防止 iOS 拖拽穿透 - 监听
resize事件重设高度(尤其横竖屏切换),但避免高频触发,可用setTimeout防抖
const overlay = document.getElementById('overlay');
function updateOverlayHeight() {
overlay.style.height = window.innerHeight + 'px';
}
window.addEventListener('resize', updateOverlayHeight);
updateOverlayHeight();
微信 / QQ 内置浏览器里的隐藏坑
这些 WebView 常把页面包裹在 iframe 或自定义容器中,window.innerHeight 返回的是外层容器高度,而非真实屏幕尺寸,导致遮罩过高或过矮。
立即学习“前端免费学习笔记(深入)”;
- 常见错误现象:遮罩盖住整个页面,但顶部留白或底部溢出一截,点击区域错位
- 解决路径:优先尝试
document.documentElement.clientHeight,若仍不准,fallback 到screen.height(仅作兜底,精度低但至少不漏) - 性能影响:频繁读取
screen.height无开销,但不要在 scroll 事件里读它——它不会变,没必要 - 关键提醒:微信 Android 版(X5 内核)对
fixed支持比 iOS Safari 还差,务必加will-change: transform强制硬件加速,否则动画卡顿明显


















