必须用100dvh+focusin事件监听+absolute定位三者配合:min-h-[100dvh]设于根容器并配flex布局,focusin时记录innerHeight并动态计算bottom值,blur后延时300ms恢复fixed,同时校验activeElement防状态错位。

软键盘弹出时页面被顶起、内容错位、fixed元素飘在半空——这不是 CSS 写错了,是浏览器把 100vh 锚定在物理屏幕高度,而 visualViewport.height 已经塌了,但 CSS 不会自动重算。必须用动态单位 + 事件监听 + 布局重构三者配合,缺一不可。
用 min-h-[100dvh] 替代 min-h-screen(但仅限新浏览器)
100dvh 是唯一能随软键盘伸缩的真实视口单位,iOS 16.4+ 和 Android Chrome 109+ 已稳定支持。但它不是“开箱即用”:
-
min-h-[100dvh]必须设在根容器(如#app或body),不能只套在某个子块里 - 必须搭配
flex flex-col布局,否则min-h-[100dvh]只是“最小高度”,不触发弹性分配 - 主内容区要加
flex-1或flex-grow,底部栏加flex-shrink-0,否则键盘一弹它就被压缩变形 - 老版本 Safari 会直接忽略整条规则,需 fallback:
min-height: 100vh+ JS 动态检测visualViewport?.height
监听 focusin/blur 而非 resize
window.addEventListener('resize') 在 iOS 上触发率低于 20%,且常只在键盘收起时才触发一次;focusin 才是键盘即将弹出的最确定信号:
- 给所有
<input>、<textarea></textarea>、[contenteditable]统一绑定focusin,别漏掉动态插入的元素 -
focusin回调里立刻记录window.innerHeight,记为baseHeight;不要等setTimeout或requestAnimationFrame,否则可能错过初始高度 - 必须加
setTimeout(() => { }, 0)再读getBoundingClientRect(),否则取到的是键盘弹出前的旧坐标 -
blur后延时 300ms 恢复,给键盘收起留出时间;检查window.innerHeight > baseHeight * 0.95再执行恢复逻辑,防止误判
用 position: absolute 替代 position: fixed(但必须配齐三个条件)
只改 position: fixed → absolute 会穿帮,因为 absolute 的定位基准是最近的 relative 祖先容器——如果这个容器本身高度不可控,照样错位:
立即学习“前端免费学习笔记(深入)”;
- 根容器(如
#app)必须设position: relative且min-height: 100vh(不能只写height: 100%,否则滚动或键盘弹出时塌陷) - 底部元素必须加
width: 100%,否则横屏或缩放后偏移 -
focusin后切换为position: absolute,并用window.innerHeight - inputRect.bottom算bottom值,不是写死0 -
blur后立刻恢复position: fixed,并调用el.style.removeProperty('top')和el.style.removeProperty('bottom')清除残留 inline 样式
多个输入框共用同一底部栏时最容易踩的坑
这是最易被忽略的复杂点:document.activeElement 可能异步变化,状态错位会导致底部栏反复切换或卡死不动:
- 在
focusin回调开头立刻做document.activeElement === inputEl校验,防止焦点已转移到别的元素 - 若用了
transform、overflow: hidden或position: fixed父容器,getBoundingClientRect()可能返回错误坐标,且无报错提示 - 别依赖
@media (max-height: ...)或env(keyboard-inset-bottom)单独兜底——前者无法感知键盘状态,后者安卓完全不支持 - 所有计算前确认当前
visualViewport?.height是否已更新,iOS 键盘收起后它可能延迟几百毫秒才恢复


















