直接用100dvh替代100vh是最轻量有效的起点,但必须配合min-height: 100dvh和overflow-y: auto,否则内容仍会被截断或遮挡;因100vh是静态单位,锁死于页面加载时的视口高度,不随键盘弹出更新,而100dvh能动态响应可视区域变化。

直接用 100dvh 替代 100vh 是最轻量、最有效的起点,但必须配合 min-height 和 overflow-y: auto,否则在多数机型上仍会截断内容或遮挡输入框。
为什么 100vh 在键盘弹出时完全失效
不是你写错了 CSS,而是浏览器把 100vh 锁死在“页面加载瞬间的视口高度”——软键盘一弹,window.innerHeight 在 iOS 上几乎不变,安卓上可能突降,但 100vh 值根本不更新。结果就是容器还撑着全屏高,而实际可视区被压缩,底部输入框直接消失或被遮挡。
-
100vh是静态单位,和键盘状态无关 -
height: 100vh比min-height: 100vh更危险:前者强制撑满,后者至少允许内容溢出滚动 - 即使加了
overflow-y: auto,如果父容器没设position: relative或高度基准不稳,滚动仍会失效
用 100dvh 的正确写法
100dvh(dynamic viewport height)代表“当前用户真正能看到的高度”,它随软键盘、安全区(如 iPhone 底部手势条)自动调整,Chrome 105+、Safari 16.4+ 已原生支持。
- 必须写成
min-height: 100dvh,不能只写height: 100dvh;否则键盘收起后可能留白或塌陷 - 关键子容器(如内容区)要加
overflow-y: auto和-webkit-overflow-scrolling: touch - 底部固定区域(如输入栏)需设
flex-shrink: 0,否则 flex 布局会把它压扁 - 降级方案必须存在:
@supports not (height: 100dvh) { ... }里回退到min-height: 100vh+focusin/blur监听
监听 visualViewport 动态调整定位
这是目前唯一能实时拿到键盘高度的可靠方式,比 resize 或 focusin 精准得多——resize 在 iOS 上触发率低于 20%,focusin 触发时键盘还没展开。
立即学习“前端免费学习笔记(深入)”;
- 初始化时存基准值:
const baseHeight = visualViewport.height - 监听变化:
visualViewport.addEventListener('resize', () => { const kbHeight = baseHeight - visualViewport.height; }) - 用
kbHeight调整按钮位置,优先用transform: translateY(${kbHeight}px),比改bottom少重排 - 注意兼容性:老版本 Safari(如 15.3)不支持,必须包裹在
if ('visualViewport' in window)判断里
别忽略的三个硬性前提
所有方案都依赖这三个底层条件,漏掉任何一个,CSS 再怎么调都白搭:
-
<meta name="viewport">必须写对且放在最前面:<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">;缺viewport-fit=cover,env(safe-area-inset-bottom)就无效 - 根容器(如
body或#app)必须设position: relative和min-height: 100dvh;只设height: 100%会导致 flex 布局塌陷 - 如果有多个输入框共用同一底部栏,必须用全局变量记录
document.activeElement,否则blur后无法准确恢复定位状态


















