100dvh 是目前最轻量有效的 css 解法,但需配合 min-h-[100dvh]、flex 布局、移除 height=device-height、禁用 fixed 定位及正确 viewport 配置才能生效。

直接用 100dvh 替代 100vh 是目前最轻量、最有效的 CSS 层面解法,但只换单位就上线,90% 的项目会继续错位。
为什么 100vh 在键盘弹出后完全不可信
iOS Safari 和多数安卓 WebView(如华为、UC)根本不会更新 vh 单位值——它被锁定在页面加载时的视口高度。键盘一弹,可视区域压缩了 300px,但容器仍按原始 667px 渲染,结果就是内容被截断、底部按钮悬空或消失。
100dvh 是动态单位,代表“无键盘状态下的完整视口高度”,Chrome 105+、Safari 16.4+ 已稳定支持。但它不是开箱即用的魔法:
- 必须写成
min-height: 100dvh,不能只写height: 100dvh;否则 flex 子项无法正确分配空间 - Tailwind 用户请用
min-h-[100dvh](Tailwind ≥ 3.3),别再用min-h-screen - 删掉
<meta>中的height=device-height—— 这个参数会让所有vh/dvh单位失效 - html 或 body 上的
h-full/min-h-full也得删,它们会干扰dvh行为
min-h-[100dvh] 必须配合 flex flex-col 使用
min-h-[100dvh] 只设最小高度,不触发弹性分配。若没配 flex 布局,主内容区照样塌成一条线,底部按钮被压缩变形。
根容器(如 #app 或 body)要这样写:
<div id="app" class="flex flex-col min-h-[100dvh]"> <main class="flex-1 overflow-y-auto"></main><footer class="flex-shrink-0"></footer> </div>
-
main加flex-1(等价于flex: 1 1 0%),确保撑满剩余空间且可独立滚动 -
footer加flex-shrink-0,否则键盘弹出会压扁按钮 - 避免文字溢出撑宽容器:可加
min-w-0到main内部文字容器
为什么 position: fixed 必须放弃
它锚定的是视觉视口(visualViewport),而软键盘弹出会压缩该视口,但浏览器不触发重排,fixed 元素位置就“冻”在旧坐标上。iOS 上尤其明显:按钮直接飘到屏幕中间,甚至被顶出可视区。
替代方案有两类,选哪个取决于语义:
- 如果底部按钮是输入操作的一部分(比如聊天页的「发送」),把它和
input放在同一position: relative容器里,用display: flex对齐 - 如果必须全局固定(如导航栏),改用
position: absolute+focusin/blur动态计算bottom值,且必须满足:
– 根容器设position: relative和min-height: 100vh
– 底部元素加width: 100%
–focusin后用setTimeout(() => {}, 0)延迟读取getBoundingClientRect()
兜底必须做:viewport 和安全区一个都不能少
哪怕用了 100dvh 和 flex,<meta name="viewport"> 配错也会让一切失效。必须写全:
<meta name="viewport" content="width=device-width, initial-scale=1.0">
禁用 maximum-scale=1 或固定 width,否则 dvh 计算失真。安全区也要兼顾:
- 底部固定区域加
padding-bottom: env(safe-area-inset-bottom) - 用
@supports (bottom: env(keyboard-inset-bottom))降级处理 iOS 原生键盘 inset - 不要依赖
window.innerHeight做判断——iOS 键盘收起后它可能延迟几百毫秒才恢复
真正容易被忽略的点是:多个输入框共用同一底部栏时,document.activeElement 可能异步变化,必须在 focusin 回调开头立刻做 === inputEl 校验,否则状态错位。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











