应使用 focusin 替代 focus 并结合 setTimeout 延迟获取位置,用 CSS 类切换代替直接修改 style.bottom,采用 env(safe-area-inset-bottom) + JS 偏移计算 bottom,blur 时需同步恢复 fixed 定位并清除内联样式,以 focusin/blur 为主、visualViewport 为辅、resize 为兜底,四层信号协同确保兼容性。

focus事件触发太早,getBoundingClientRect()拿不到真实位置
输入框 focusin 触发时,软键盘还没完全弹出,DOM 也没重绘完成,直接调 input.getBoundingClientRect() 得到的 bottom 值还是全屏状态下的,算出来的 bottom 偏移量必然错误。
- 必须加
setTimeout(() => { /* 读取位置 */ }, 0),把读取逻辑推到下一帧,确保视口已收缩、布局已更新 - 不要用
focus,改用focusin(能冒泡,方便委托),且只监听一次,避免重复绑定 - 如果页面有多个输入框共用同一个底部 fixed 栏,需用一个全局变量记录当前聚焦的
input,防止 blur 后恢复错位
直接改 style.bottom 会强制同步重排,卡顿明显
在 handler 里写 el.style.bottom = '20px' 看似简单,但浏览器会立即触发 layout → paint → composite 流程,尤其低端机上滚动中再聚焦,肉眼可见卡顿。
- 优先用
el.classList.add('keyboard-active')切换预设 CSS 类,所有样式(position、bottom、transform)都在 CSS 里定义好 - CSS 类里不要用
vh或calc(100vh - Xpx)做 bottom 值——软键盘弹出后100vh不变,它还是物理屏幕高度 - 推荐用
bottom: env(safe-area-inset-bottom)+ JS 动态补一个固定偏移(如44px),比纯 JS 计算更稳
blur 后不恢复 fixed,滚动时定位彻底错乱
只处理聚焦、不处理失焦,是线上最常见翻车点。用户点完输入框切走、或点击空白处收起键盘,此时若 fixed 元素还卡在 absolute,一滚动,它就跟着文档流跑,甚至“粘”在某个段落上。
-
blur回调里必须同步恢复position: fixed和原始bottom - 别依赖
setTimeout延迟恢复——键盘收起后innerHeight恢复有延迟(iOS 尤其明显),应结合visualViewport的height回升判断,或降级为监听focusout+document.activeElement !== input - 恢复前先
el.style.removeProperty('top')和el.style.removeProperty('bottom'),避免残留 inline 样式干扰
为什么不能只监听 resize?
resize 在 iOS Safari 上对软键盘响应极不可靠:有时压根不触发,有时延迟 300ms 以上,且安卓各 WebView 行为不一致。单纯靠它做判断,等于在赌浏览器心情。
立即学习“前端免费学习笔记(深入)”;
- 必须以
focusin/blur为主干,visualViewport为增强(Chrome 61+/Safari 15.4+),resize仅作兜底 - 监听
visualViewport时,用height变化阈值(如Math.abs(prevHeight - currentHeight) > 150)判断键盘状态,比单纯看是否变化更抗噪 - 首次加载就要绑定
visualViewport.addEventListener('resize', handler),否则第一次弹键盘可能错过事件
真正难的不是写几行 JS,而是把 focus/blur/visualViewport/resize 这四层信号对齐,再把它们和 CSS 类、safe-area、滚动锚点这些细节串成一条不掉链子的流水线。漏掉任意一环,都可能在某台 iPhone 上静默失效。


















