100dvh是解决键盘弹出导致布局错位的最轻量css方案,因其动态响应可视区域变化;需配合flex布局、min-height/max-height设置、viewport-fit=cover及env(safe-area-inset-bottom)等综合配置,否则仍会错位。

直接用 100dvh 替代 100vh 是目前最轻量、最有效的 CSS 层面解法,但必须配合 flex 布局约束和正确的 viewport 配置,否则照样错位。
为什么 100vh 在键盘弹出后完全不可信
iOS Safari 和多数安卓 WebView(如华为、UC)根本不会更新 vh 单位值——它被锁定在页面加载时的视口高度。键盘一弹,可视区域压缩了 300px,但容器仍按原始 667px 渲染,结果就是内容被截断、底部按钮悬空或消失。
-
100dvh是动态单位,代表“无键盘状态下的完整视口高度”,Chrome 105+、Safari 16.4+ 已稳定支持 - 不能只写
height: 100dvh,必须用min-height: 100dvh; max-height: 100dvh,再配overflow-y: auto,否则 flex 子项无法正确分配空间 - 所有依赖全屏高度的容器(如根
#app、body)都得改,包括 Tailwind 的min-h-screen(它编译为min-height: 100vh)
只换单位还不够:必须用 flex 锚定子项行为
100dvh 解决了父容器高度问题,但子元素仍可能被压缩或错位,关键在于 flex 容器内部的弹性控制。
- 根容器要设
display: flex; flex-direction: column; min-height: 100dvh - 主内容区加
flex: 1; overflow-y: auto,确保它撑满剩余空间且可独立滚动 - 底部固定区域(如提交按钮)必须加
flex-shrink: 0,否则会被 flex 压缩变形 - 删掉
<meta>中的height=device-height—— 这个参数会让所有vh/dvh单位失效
为什么 position: fixed 必须放弃
它锚定的是视觉视口(visualViewport),而软键盘弹出会压缩该视口,但浏览器不触发重排,fixed 元素位置就“冻”在旧坐标上。iOS 上尤其明显:按钮直接飘到屏幕中间,甚至被顶出可视区。
- 别试图用
@media (max-height: ...)降级 —— 键盘弹出不改变媒体查询条件 - 别监听
resize—— iOS 微信 WebView 触发率低于 20%,安卓 Chrome 可能延迟 300ms - 真正可靠的信号是
focusin(不是focus,它冒泡更可靠),配合setTimeout(() => {}, 0)读取重排后的getBoundingClientRect() - 如果底部按钮语义上就是输入操作的一部分(比如聊天页的「发送」),最稳方案是把它和
input放在同一position: relative容器里,用display: flex对齐
兜底必须做:viewport 和安全区一个都不能少
即使用了 100dvh 和 flex,<meta name="viewport"> 配错也会让一切失效。
- 必须写全:
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> - 禁用
maximum-scale=1.0或user-scalable=no—— 否则 iOS 键盘收起后visualViewport.height不恢复 - 全面屏设备需预留手势条:
padding-bottom: env(safe-area-inset-bottom),否则按钮会被底部小白条盖住 - 老版本兼容:用
padding-bottom: max(env(safe-area-inset-bottom), 20px),避免不支持env()的浏览器整条规则失效
最易被忽略的一点:多个输入框共用同一底部栏时,focusin 和 blur 事件必须用全局变量记录当前 document.activeElement,否则切换输入框会重复计算或漏恢复定位状态。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











