100vh在键盘弹出后“变小”或错位,因其是页面加载时的静态快照值,iOS Safari不更新documentElement.clientHeight,安卓WebView更新不同步,导致布局与可视区脱节;应改用100dvh并严格配viewport-fit=cover、@supports检测及min/max-height组合。

因为 vh 是静态快照,不是实时视口高度。 它在页面加载时就固定了 1vh = Xpx 的换算关系,键盘弹出后浏览器既不更新这个映射,也不保证触发 resize,导致容器高度卡死、内容被截、fixed 元素飘浮。
为什么 100vh 在键盘弹出后“变小”或“错位”
这不是你写错了 CSS,而是浏览器底层机制决定的:
- iOS Safari 根本不更新
document.documentElement.clientHeight,100vh始终等于初始物理屏高(比如 844px),键盘盖上来,它也不变 - 部分安卓 WebView 虽然会改
clientHeight,但 CSS 引擎仍沿用旧的1vh像素值,导致布局渲染和实际可视区脱节 - 结果就是:flex 容器塌陷、
position: fixed按旧高度锚定、底部按钮浮在键盘上方、滚动位置跳变
100dvh 能直接替换 100vh 吗
能,但有硬约束,漏掉任何一条都会降级失败:
- 必须加
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">——缺这个,iOS 直接忽略dvh - 必须用
@supports (height: 100dvh)包裹样式,否则老浏览器解析报错或回退失效 - 不能写
height: 100dvh,得用min-height: 100dvh; max-height: 100dvh; overflow-y: auto——否则内容超长无法滚动 -
getComputedStyle(el).height返回的是字符串"100dvh",不是像素值,不能用于 JS 计算
focusin 比 resize 更可靠,但容易踩坑
单纯监听 window.addEventListener('resize') 在真机上大概率漏掉首次键盘弹出,或收起后延迟触发:
立即学习“前端免费学习笔记(深入)”;
- 只监听
input和textarea的focusin(不是focus,防冒泡干扰) - 回调里必须加
setTimeout(() => {}, 0),否则getBoundingClientRect()或window.innerHeight取到的是键盘弹出前的旧值 - 差值大于 150px 才判定为键盘弹出——太小易误判地址栏收起或滚动抖动
-
blur后不能立刻恢复状态,要setTimeout(() => {}, 100)等 iOS 键盘动画结束,否则页面闪一下
真正难的不是选哪个单位,而是所有方案都依赖 viewport-fit=cover 这个开关——漏掉它,dvh、env(keyboard-inset-bottom)、visualViewport 全部失效;但它又可能在某些安卓定制 WebView 里引发渲染异常,得实机逐个测。


















