唯一可靠解法是用@supports(min-height:100dvh){.section{min-height:100dvh}}兜底100vh,因100dvh能实时响应ios safari/微信x5地址栏、键盘导致的视口变化,而100vh在移动端为静态计算,ssr中更会失效。

直接用 min-h-[100dvh] 替代 vh-100 或 min-vh-100,并严格包裹在 @supports (min-height: 100dvh) 块内——这是目前唯一能同时避免 iOS Safari 15.x、微信 X5 内核和 SSR 渲染中“页面塌成一条线”的解法。
为什么 vh-100 在移动端滚动时会错位
iOS Safari 和微信 X5 内核对 100vh 的计算是静态的:只在页面加载瞬间按物理屏高(如 iPhone 14 的 844px)取值。地址栏收起、键盘弹出、页面滚动后视口实际变大或变小,但 vh 值卡死不动,导致容器高度与真实可视区域脱节。你看到的“滚动后留白”或“内容被裁切”,本质是布局高度没跟上视口变化。
-
vh-100编译为height: 100vh,强制等高,不随滚动响应 -
min-vh-100编译为min-height: 100vh,虽允许内容撑开,但底层仍是静态vh,同样卡住 - SSR 渲染时服务端无
window,100vh直接 fallback 为空,首屏高度丢失
如何安全启用 100dvh 并兜底老环境
100dvh 是动态视口单位,iOS 16.4+、Chrome 109+、Firefox 117+ 已稳定支持,它会随地址栏/键盘实时更新。但它不能裸写,必须靠 @supports 控制生效边界:
- 错误写法:
.section { min-height: 100vh; min-height: 100dvh; }→ 老 Safari 忽略第二行,新 Safari 仍走第一行,无效 - 正确写法:
.section { min-height: 100vh; } @supports (min-height: 100dvh) { .section { min-height: 100dvh; } }→ 兜底在外,增强在内 - Tailwind 用户需确保启用了 arbitrary value(v3.3+ 默认开启),直接写
min-h-[100dvh] - 构建工具如 PostCSS 若自动把
dvh降级为vh(例如postcss-100vh-fix插件),必须禁用
滚动容器内 100dvh 不生效的常见原因
百分比类高度(包括 100dvh)依赖包含块(containing block)有明确高度。若父容器没设高度,子元素的 100dvh 实际计算为 0:
- 对
position: fixed或absolute元素,别用min-h-[100dvh],改用top-0 bottom-0更可靠 - Bootstrap
.offcanvas中,.offcanvas-body应用max-h-[100dvh]而非h-[100dvh],防止内容溢出时被截断 - 若用了自定义 header,必须放在
.offcanvas-header内,不能塞进.offcanvas-body,否则100dvh减法错位 - 绝对定位抽屉菜单,其祖先(如
body或直接父div)必须先设min-h-[100dvh],否则子元素无参考高度
禁用全局滚动时,局部滚动失效怎么办
当设置 html, body { overflow-y: hidden; height: 100%; } 后,局部容器(如 .modal-body)若仍不滚动,通常因为:
-
.modal-body没设max-height或固定高度,仅overflow-y: auto不触发滚动 - 内容未真实溢出(测试时至少塞 10 行文本,空
<div> 不会触发) <li>选择器权重不够,被 Bootstrap 默认样式覆盖(检查 DevTools 的 Computed 面板,确认 <code>overflow-y确实是auto) - 在 iOS 上,还需额外加
body { position: fixed; top: 0; width: 100%; }才能真正锁死滚动,否则仍可能穿透
最易被忽略的一点:即使你写了 min-h-[100dvh],若外层没配 flex flex-col 或 height: 100% 的祖先链,它依然不会占满——动态单位不是魔法,它依赖高度传递路径完整。











