聊天输入框用 position: fixed 被键盘顶穿是因为其锚定视觉视口底部,而软键盘弹出时该视口在 iOS 基本冻结、Android 缩窄却不触发重绘;window.innerHeight 不变,getBoundingClientRect() 返回旧坐标,导致输入框卡在键盘下。

为什么聊天输入框用 position: fixed 一定会被键盘顶穿
不是你 CSS 写错了,而是 position: fixed 锚定的是「视觉视口(visual viewport)」底部,而软键盘弹出时,这个视口在 iOS 上基本冻结、在 Android 上可能缩窄但不触发可靠重绘。window.innerHeight 在 iOS Safari 中几乎不变,getBoundingClientRect() 返回的仍是弹出前坐标——结果就是输入框卡在键盘底下,用户完全看不见自己刚打的字。
优先用 position: sticky 替代 fixed 的硬条件
position: sticky 不脱离文档流,天然响应内容滚动和键盘推起行为,是目前最轻量、副作用最少的解法,但必须满足三个前提:
- 父容器(比如
.chat-container)必须设min-height: 100vh或height: 100vh,不能只靠flex: 1或overflow: auto偷懒 - 父容器不能有
transform、overflow: hidden或will-change,否则sticky直接失效 - 输入框本身设
position: sticky; bottom: 0;,并加z-index: 1000防被消息列表遮挡
兼容性没问题:iOS 15.4+、Chrome 90+ 全支持;旧版本可 fallback 到 absolute + JS 动态计算。
监听 focusin 动态调整 bottom 的关键细节
别用 resize,它在 iOS 上几乎不触发;也别在 focusin 回调里立刻读高度——键盘还没展开,getBoundingClientRect().bottom 还是旧值。
立即学习“前端免费学习笔记(深入)”;
- 必须加
setTimeout(() => { /* 读坐标 */ }, 0),让浏览器完成 layout 更新 - 计算公式是:
window.innerHeight - inputRect.bottom,不是inputRect.top,也不是写死200px - 多个输入框共用同一底部区域时,用
document.activeElement判断当前焦点,避免重复切换或漏恢复 -
blur后立刻清除内联bottom并恢复position: fixed,否则滚动时输入框会消失
安卓 WebView 和微信内置浏览器的兜底方案
安卓完全不支持 env(keyboard-inset-bottom),微信 WebView 对 scrollTo(0, 0) 有节流,直接调用常无效。
- 最稳 fallback 是给最外层容器(如
#app)加padding-bottom: 60px(按输入框实际高度设),并确保box-sizing: border-box - 若需动态避让,监听
focusin后执行setTimeout(() => window.scrollTo(0, 0), 100),比requestAnimationFrame更兼容老机型 - 结构上强绑定的场景(如“发送”按钮紧贴输入框右侧),直接把按钮和
input放进同一个display: flex容器,用margin-left: auto对齐——完全绕过定位问题
真正麻烦的不是怎么写 CSS,而是不同 WebView 对 visualViewport、focusin 时机、scrollTo 节流的实现差异。一个方案很难通吃,得按目标环境组合使用。


















