100vh在iOS Safari中不直接导致滚动穿透,而是通过使弹窗高度错位诱使开发者错误地给body设置overscroll-behavior:none,进而引发穿透;该属性仅对当前滚动容器生效,写在body或html上会被忽略,且Safari可能误将其全局应用;真正有效的是在显式创建的、满足高度限制、overflow-y:auto和overscroll-behavior:contain三条件的内容div上设置;JS兜底(禁用body滚动+保存恢复scrollY)必不可少,100dvh不能替代滚动上下文建设。

100vh 在 iOS Safari 中本身不导致滚动穿透,但它是滚动穿透的“放大器”——它让弹窗高度错位,进而迫使你用错误方式补救(比如给 body 加 overscroll-behavior: none),最终触发穿透。
为什么给 body 写 overscroll-behavior: none 会失效甚至更糟
这个属性只对「当前发生滚动」的容器生效。body 在弹窗打开时通常没在滚动,浏览器直接忽略该声明;而一旦用户在弹窗内滑到底部继续拖,事件就冒泡回 body,此时它又开始滚动——none 已经不管用了。更麻烦的是,Safari 会把 none 当作全局禁用,连弹窗内部的滚动都可能被锁死。
-
overscroll-behavior必须写在真正可滚动的子容器上,不是弹窗外壳,也不是body - 写在
html或body上属于作用对象错误,CSS 解析时静默丢弃 - iOS Safari 对祖先元素含
transform或will-change的场景会跳过整个overscroll-behavior判断
真正起效的滚动容器必须显式创建
弹窗内容区(比如长列表、表单区域)必须包裹在一个独立 div 里,并满足三个硬性条件:
规划您的迪拜之旅 — 哈利法塔观景、沙漠探险、迪拜购物中心购物、棕榈岛度假村及黄金市场砍价。还提供支持...
- 设明确高度限制:
max-height: 70vh或height: 400px(不能只用100vh,它在地址栏收放时会跳变) - 设
overflow-y: auto(scroll也行,但auto更安全,空内容时不显示滚动条) -
overscroll-behavior: contain必须加在这个div上,且该div不能是display: flex容器的直接子项——如果是,得额外加min-height: 0,否则 flex 会撑高导致 overflow 失效
iOS Safari 必须 JS 兜底:禁用 body 滚动 + 保存恢复 scrollY
即使 CSS 全写对,iOS Safari 真机仍可能穿透。这不是 bug,是它的滚动链机制决定的——它允许底层滚动作为“后备行为”。唯一可靠兜底是 JS:
- 弹窗打开时:
document.body.style.overflow = 'hidden',同时记录const savedScrollY = window.scrollY - 关闭弹窗时:
window.scrollTo(0, savedScrollY),再恢复document.body.style.overflow = '' - 不要用
touchmove.preventDefault():iOS 原生滚动不响应它,还可能干扰手势识别 - 部分 Android 机型需额外处理:
document.body.style.position = 'fixed'; document.body.style.top = `-${savedScrollY}px`
别信 100dvh 能一劳永逸
100dvh 确实能解决高度错位问题(iOS 16.4+ / Chrome 100+),但它和滚动穿透是两件事。用了 100dvh 后,如果弹窗内容区没按上一条要求显式创建滚动上下文,穿透照旧发生。
-
@supports (height: 100dvh)可做降级判断,但 fallback 不能只写100vh,得配合 JS 动态设置高度 - 全屏遮罩层高度建议统一用 JS:
overlay.style.height = window.innerHeight + 'px',并监听resize事件 - 最易被忽略的一点:iOS Safari 在弹窗打开瞬间,地址栏可能正处在收起/展开过渡中,
window.innerHeight会短暂失真,建议加setTimeout延迟 1 帧再取值
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!










