100vh错位因始终等于物理屏幕高度而非可视区高度;安卓下window.innerHeight缩水而100vh不变,iOS则键盘覆盖导致fixed锚点失效;应改用100dvh并配viewport-fit=cover、@supports检测及min-height/max-height组合。

为什么100vh在键盘弹出后会错位
因为100vh始终等于设备物理屏幕高度,不是当前可视区域高度。软键盘弹出时,window.innerHeight在安卓上会明显缩水(比如从844px缩到500px),但100vh仍按原始值计算;iOS更极端——它甚至不压缩window.innerHeight,只是把键盘盖在页面上层,导致fixed元素锚点失效、底部按钮“飘”在半空。
用100dvh替代100vh要配齐三样东西
100dvh是动态视口高度单位,现代浏览器(Safari 16.4+、Chrome 109+)支持,但它不会自动生效,必须同时满足:
-
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">——缺这个,iOS部分机型直接忽略dvh -
@supports (height: 100dvh)包裹样式——老浏览器回退到100vh或min-height: 100% - 避免对根容器设
height: 100vh——改用min-height: 100dvh; max-height: 100dvh; overflow-y: auto,防内容撑爆或塌陷
env(keyboard-inset-bottom)只适合微调,别指望它撑布局
env(keyboard-inset-bottom)在iOS 16.4+中返回键盘真实高度(单位px),但它有硬限制:
- 仅iOS Safari支持,安卓无等效变量
- 必须配合
viewport-fit=cover,否则值恒为0 - 不能单独用它撑高容器(比如
height: env(keyboard-inset-bottom)无效),只适合偏移类定位:bottom: calc(env(keyboard-inset-bottom, 0px) + 10px) - 降级写法要加
constant(safe-area-inset-bottom, 0)兼容旧Safari
resize事件监听不可靠,focusin才是唯一可信入口
单纯监听window.addEventListener('resize')在真机上大概率漏掉首次键盘弹出,或在收起后延迟触发。实际必须以focusin为起点:
立即学习“前端免费学习笔记(深入)”;
- 只监听
input和textarea的focusin(不是focus,防冒泡干扰) - 聚焦后加
setTimeout(() => {}, 0)再读window.innerHeight或getBoundingClientRect(),否则取到的是键盘弹出前的旧值 - 差值大于150px才判定为键盘弹出——太小易误判滚动或地址栏收起
-
blur后不能立刻恢复状态,要setTimeout(() => {}, 100)等iOS键盘动画结束,否则页面闪一下
真正难的不是换哪个单位,而是所有方案都依赖viewport-fit=cover这个开关——漏掉它,dvh、env()、visualViewport全失效;但它又可能在某些安卓定制WebView里引发其他渲染异常,得实机逐个测。


















