应使用 flex 布局将按钮作为文档流内最后一个子元素自然排布到底部,配合 min-height: 100dvh、display: flex; flex-direction: column; 和 flex: 1 或 margin-top: auto;iOS 16.4+ 可用 @supports (bottom: env(keyboard-inset-bottom)) { bottom: env(keyboard-inset-bottom, 0px) } 原生避让;兜底用 JS 监听 focusin/blur 动态计算 bottom 值,避免 position: fixed。

不能靠 position: fixed,这是浏览器重定义 visualViewport 导致的定位锚点失效,不是你 CSS 写错了。
用 flex 布局把按钮“推到底部”,而不是“钉在视口底”
核心是让按钮作为文档流内最后一个子元素,由父容器用 flex 规则自然排布到底部,完全不依赖视口尺寸变化。
-
min-height: 100dvh是首选,比100vh更准(避开 iOS 地址栏干扰);老浏览器 fallback 可加 JS 检测并设min-height: 100vh - 根容器(如
#app或form)必须设display: flex; flex-direction: column; - 主内容区(如
.form-body)设flex: 1;或margin-top: auto;,把剩余空间撑开 - 提交按钮作为同级兄弟节点,普通块级元素,不能包在 flex 子项里再设
align-self: flex-end——那只是视觉错觉,仍会被顶 - 按钮本身不能有
position: fixed或absolute,否则脱离流,兜底失效
iOS 16.4+ 可用 env(keyboard-inset-bottom) 做纯 CSS 原生避让
这是目前最干净的 iOS 专用解法,但必须三件套齐备,缺一不可:
-
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">必须存在,否则变量不注入 - 写法必须是
bottom: env(keyboard-inset-bottom, 0px),0px是收起键盘后的 fallback,漏掉会导致按钮悬空 - 整条规则必须包裹在
@supports (bottom: env(keyboard-inset-bottom))里,否则旧版 Safari 会忽略整条规则甚至解析失败 - 安卓 WebView 完全不支持,别混用
svh或100vh计算,比如bottom: calc(100svh - 100px)是无效逻辑
监听 focusin/blur + visualViewport 动态调整(兼容兜底)
当 flex 或 env 都不可用时(如微信内置 Webview、旧版 Android),这是覆盖机型最广的 JS 方案,但关键在锚点控制:
立即学习“前端免费学习笔记(深入)”;
- 根容器需设
position: relative和min-height: 100vh(不能只写height: 100%,键盘弹出会塌陷) -
focusin后用setTimeout(() => {}, 0)等布局稳定,再读input.getBoundingClientRect().bottom - 计算 bottom 值用
window.innerHeight - inputRect.bottom,不是top,也不是写死数值 -
blur后立刻恢复position: fixed; bottom: 0,并用el.style.removeProperty('bottom')清除内联样式,否则滚动时按钮消失 - 别监听
resize或媒体查询——它们根本捕获不到键盘弹出的真实状态
真正难的不是写哪一行代码,而是判断当前环境该走哪条路径:iOS 新版优先 env,老设备或混合 Webview 必须 JS fallback,而 flex 是所有方案里语义最正、无障碍最友好、且天然抗键盘干扰的底层结构。别让按钮脱离表单 DOM 流,那是所有问题的起点。


















