最简有效解法是用100dvh替换100vh,但必须配合viewport-fit=cover、@supports降级及min-height/max-height组合,否则ios或安卓webview中仍会错位。

直接用 100dvh 替换 100vh 是最简有效的解法,但必须配齐 viewport-fit=cover、@supports 降级和 min-height/max-height 组合,否则在 iOS 或安卓 WebView 中依然会错位。
为什么 100vh 在键盘弹出时失效
100vh 固定等于设备物理屏幕高度,不是当前可视区域高度。软键盘弹出时,iOS 不改 window.innerHeight,只是覆盖页面;安卓则可能压缩该值,但 CSS 不重算 vh。结果就是容器撑满“假高度”,内容被截断或底部输入框消失。
- iOS Safari 16.4+ 和 Chrome 105+ 支持
100dvh,它始终代表用户当前真正看到的高度 - 仅写
height: 100dvh不够——必须用min-height: 100dvh; max-height: 100dvh; overflow-y: auto防塌陷或溢出 - 漏掉
<meta name="viewport" content="..., viewport-fit=cover">,dvh会被 Safari 忽略
env(keyboard-inset-bottom) 只能微调定位,不能撑高容器
env(keyboard-inset-bottom) 在 iOS 16.4+ 返回真实键盘高度(单位 px),但它只适合偏移类定位,比如底部按钮上浮,**不能用于设置容器高度**。
- 正确用法:
bottom: env(keyboard-inset-bottom, 0px),配合@supports (bottom: env(keyboard-inset-bottom))降级 - 错误用法:
height: env(keyboard-inset-bottom)—— 值恒为0,无效 - 安卓完全不支持该变量,不能单独依赖;也别和
svh混用,比如bottom: calc(0px + 100svh)是语义错误
focusin + visualViewport 是现代浏览器最稳的监听组合
window.resize 在 iOS 上大概率不触发键盘弹出事件,focusin 才是唯一可靠入口,再叠加 visualViewport.height 获取真实可视高度。
- 初始化存基准值:
const baseHeight = visualViewport?.height || window.innerHeight - 监听变化:
visualViewport.addEventListener('resize', () => { const kbHeight = baseHeight - visualViewport.height; }) - 聚焦后用
setTimeout(() => {}, 0)确保 DOM 更新完成再读坐标,避免取到旧值 - 不支持
visualViewport的环境(如 iOS 15.3)必须降级到focusin+window.innerHeight差值判断(阈值建议 >150px)
absolute 替代 fixed 要满足三个硬条件
position: absolute 锚定在父容器而非视口,天然绕过键盘顶起问题,但不是设了 bottom: 0 就完事。
- 父容器必须有
position: relative且min-height: 100vh(不能只写height: 100%,易塌陷) - 底部元素必须加
width: 100%,否则横屏或缩放后错位 - 必须监听
focusin和blur,动态重算bottom值:footer.style.bottom = <code>${window.innerHeight - inputRect.bottom}px -
blur后要setTimeout(() => {}, 100)再恢复bottom: 0,否则 iOS 键盘收起动画未结束就重置,页面闪一下
真正麻烦的不是选哪个方案,而是所有方案都依赖 viewport-fit=cover 这个开关——漏掉它,dvh、env()、visualViewport 全失效;但它又可能在某些安卓定制 WebView 里引发缩放异常,得实机逐个测。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











