用 100dvh 替代 100vh 是解决移动端 fixed 底部栏遮挡内容的最可靠 css 方案,需配合 viewport-fit=cover,并降级至 100vh;同时推荐 sticky 或 flex 布局替代 fixed,避免视口动态变化问题。

直接用 height: 100vh 做全屏容器 + position: fixed 底部栏,一定会遮挡内容——这不是你写错了,是浏览器把地址栏、软键盘都算进“视口”里了,而 fixed 元素又不响应这些变化。
为什么 100vh + fixed 在移动端必然出问题
iOS Safari 和多数安卓 WebView 中,100vh 取的是设备物理高度(含地址栏),但用户实际可见区域会随地址栏收起/键盘弹出动态收缩;position: fixed 锚定的是初始视口坐标,不会重算。结果就是:底部栏“钉死”在屏幕物理底边,而输入框一聚焦,键盘顶上来,它就盖住 input。
-
100vh在 iOS 上比真实可视区高约 60–100px(地址栏+手势条) -
fixed元素脱离文档流,主内容容器根本不知道它存在,不会自动留空 - 微信 WebView、旧版 Chrome 对
resize事件支持极差,JS 监听 fallback 容易失效
用 min-height: 100dvh 替代 height: 100vh
100dvh 是动态视口单位,iOS 16.4+ 和 Chrome 100+ 原生支持,会随地址栏/键盘自动调整高度。它是目前最轻量、最可靠的 CSS 解法。
- 必须配合
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">,否则env()和dvh都不生效 - 降级写法:
@supports (height: 100dvh) { .page { height: 100dvh; } },不支持时自动回退到100vh - Android 旧 WebView 忽略
100dvh无副作用,会 fallback 到声明的下一个规则(如height: 100vh)
底部栏别用 fixed,改用 sticky 或 flex 推下去
position: sticky; bottom: 0 不脱离文档流,能随父容器滚动自然响应键盘收缩;display: flex; flex-direction: column 则完全绕开视口单位依赖,靠布局规则推到底部。
- 用
sticky时,父容器必须设overflow-y: auto和明确高度(如max-height: 60vh),否则退化为static - 用
flex时,底部栏必须是主容器的兄弟节点(不是子元素嵌套),且中间内容区加flex: 1占满剩余空间 - 绝对不要给
body加padding-bottom——会影响IntersectionObserver和滚动判断
键盘弹出时确保输入框可见的最小补救措施
scrollIntoView 对 fixed 元素基本无效,iOS 上常只滚到键盘边缘。真正可控的是提前预留滚动偏移。
- 给
input父容器设scroll-margin-bottom: 120px(预估键盘高度),再调inputEl.scrollIntoView({ block: 'nearest', behavior: 'smooth' }) - 微信安卓版不支持
scroll-margin-bottom,需 JS fallback:window.scrollTo(0, inputEl.getBoundingClientRect().top - 80) - 所有可聚焦元素必须加
tabindex="0",否则部分安卓 WebView 无法触发 focus 事件
最麻烦的点不在怎么写,而在环境判断:iOS 16.4 以下、微信内置浏览器、某些安卓定制 WebView,它们对 dvh、sticky、scroll-margin-bottom 的支持是碎片化的。一个页面里往往要同时跑三套逻辑,靠 @supports 和 UA 检测兜底,而不是指望某一种写法通吃。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











