100vh在键盘弹出后失效是因为它始终等于设备物理屏幕高度而非当前可视区域高度;100dvh可动态响应键盘变化,需配合viewport-fit=cover使用,并用@supports渐进增强,env(keyboard-inset-bottom)适合iOS微调定位,resize事件需多重校验防误判。

为什么100vh在键盘弹出后会失效
因为100vh始终等于设备物理屏幕高度,不是当前可视区域高度。软键盘弹出时,浏览器压缩visualViewport.height,但100vh不会跟着变——比如 iPhone 13 上初始100vh = 844px,键盘弹出后visualViewport.height可能只剩500px,而100vh仍是844px,导致fixed元素错位、内容上浮。
用100dvh替代100vh(现代浏览器首选)
100dvh是“dynamic viewport height”,iOS 16.4+ / Android Chrome 109+ 开始支持,它会随软键盘自动更新。直接替换即可,无需JS:
body {
height: 100dvh; /* 替换原来的 100vh */
}
.footer {
position: fixed;
bottom: 0;
width: 100%;
}- 必须搭配
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">,否则部分iOS机型不触发更新 -
@supports (height: 100dvh)可做渐进增强,老版本回退到100vh+ JS兜底 - 注意:安卓WebView兼容性仍不稳定,部分厂商定制系统(如华为EMUI WebView)可能忽略
dvh
env(keyboard-inset-bottom) 直接适配键盘高度(iOS 16.4+)
这个环境变量在键盘弹出时自动返回键盘高度(单位px),比dvh更精准,且不依赖视口重算:
.send-btn {
position: fixed;
bottom: env(keyboard-inset-bottom, 0px);
}- 仅iOS Safari 16.4+ 支持,安卓暂无等效变量
- 必须配合
viewport-fit=cover,否则变量值恒为0 - 不能单独用它撑高容器,只适合微调定位偏移(如底部按钮上浮)
- 降级写法:
bottom: calc(env(keyboard-inset-bottom, 0px) + 10px);避免紧贴键盘边缘
resize事件不可靠,必须加防抖和阈值判断
单纯监听window.addEventListener('resize', ...)在iOS上常漏掉首次弹出,或在键盘收起后延迟触发。实际要用三重保障:
立即学习“前端免费学习笔记(深入)”;
- 监听
focusin到<input>或<textarea></textarea>,记录当前window.innerHeight - 监听
resize,但只在window.innerHeight比初始值小150px以上时才判定为键盘弹出 - 加
setTimeout(..., 0)确保DOM重排完成后再读取尺寸,避免getBoundingClientRect()取到旧值 - 键盘收起后不要立刻恢复,延时
300ms再检查innerHeight是否回升,防止误判
真正难的不是写对某一行CSS,而是不同机型对visualViewport、resize、focusin的触发节奏完全不一致——得靠多事件交叉验证,而不是押宝单一API。


















