最干净的纯CSS解法是用env(keyboard-inset-bottom)配合viewport-fit=cover和@supports降级,但仅限iOS Safari 16.4+;兼容性更广的方案是position: sticky或flex布局;JS监听focusin/blur动态调整仍是必要兜底。

直接用 position: fixed 做底部操作栏,在虚拟键盘弹出时必然被遮挡——这不是你 CSS 写错了,而是浏览器把 fixed 锚定在「视觉视口(visual viewport)」底部,而键盘一弹,这个视口就塌了,但定位值不重算。
用 env(keyboard-inset-bottom) 让 iOS 自动避让(16.4+)
这是目前最干净的纯 CSS 解法,但仅限 iOS Safari 16.4 及以上:
-
bottom: env(keyboard-inset-bottom, 0px)会自动返回键盘高度,键盘收起时回退到0px - 必须搭配
<meta name="viewport" content="viewport-fit=cover">,否则不生效 - 必须用
@supports降级,否则旧版 Safari 会忽略整条规则:@supports (bottom: env(keyboard-inset-bottom)) { .footer { bottom: env(keyboard-inset-bottom, 0px); } } - 别和
svh混用,bottom: calc(0px + 100svh)是错的——svh是高度单位,env(keyboard-inset-bottom)是偏移量
用 position: sticky 替代 fixed(兼容性更广)
sticky 不脱离文档流,天然规避键盘导致的锚点失效问题,但有硬前提:
- 父容器必须设
height: 100vh或min-height: 100vh,且overflow-y: auto - 底部元素写
position: sticky; bottom: 0;,不是top: 0 - iOS 15.4+、Chrome 90+ 支持良好;老版本需 fallback 到 JS 补位
- 若父容器用了
transform或overflow: hidden,sticky会失效且无提示
用 flex 布局把底部“推”进文档流(最稳兜底方案)
完全绕开视口锚点,靠布局流维持位置,兼容所有现代浏览器:
立即学习“前端免费学习笔记(深入)”;
- 根容器设
display: flex; flex-direction: column; height: 100vh - 内容区用
flex: 1; overflow-y: auto独立滚动 - 底部区域作为兄弟节点,不设
position,只加flex-shrink: 0 - 避免对
body直接设height: 100vh,iOS Safari 下可能因地址栏导致计算偏差,推荐用min-height: 100dvh或 JS fallback
监听 focusin + setTimeout 动态切 absolute(JS 必须项)
纯 CSS 方案总有盲区,JS 补位是实际项目中无法跳过的步骤:
- 监听所有
input和textarea的focusin(不是focus,冒泡更可靠) -
focusin回调里加setTimeout(() => { ... }, 0),确保 DOM 重排完成后再读getBoundingClientRect() - 用
window.innerHeight - inputRect.bottom算 bottom 偏移,而非直接读innerHeight -
blur后立刻恢复position: fixed; bottom: 0,并调用el.style.removeProperty('top')和el.style.removeProperty('bottom')清除 inline 样式 - 所有计算前先确认
document.activeElement === inputEl,防止异步焦点转移导致状态错位
最容易被忽略的是:没有一种方案能单独扛住所有机型和系统版本。iOS 16.4+ 可以靠 env(keyboard-inset-bottom),安卓必须依赖 JS 或结构降级;而 flex 布局看似简单,一旦父容器高度失真(比如用了 100vh 却没处理安全区),底部照样飘走。


















