100vh + fixed在移动端必然失效,因其锚定设备物理高度而非当前可视区域,地址栏收起、键盘弹出等均不更新该值;应改用min-height: 100dvh配合flex布局与@supports降级,并规避transform等隐式层叠上下文干扰。

为什么100vh + fixed在移动端必然失效
因为 100vh 锚定的是设备物理高度,不是用户当前可见区域。iOS Safari 地址栏收起、安卓 Chrome 工具栏隐藏、软键盘弹出——这些都会压缩「视觉视口(visual viewport)」,但 100vh 值纹丝不动。结果就是:fixed 底部导航被顶到屏幕中央,或直接被键盘盖住。这不是你 CSS 写错,是单位本身就不该用在这里。
用 100dvh 替换容器高度,而非直接修 fixed
别试图靠 bottom: calc(100vh - 300px) 这类硬编码去“猜”键盘高度——它不可靠且无法响应变化。正确做法是让整个布局容器适配动态视口:
-
body或根容器(如#app)设min-height: 100dvh,而不是height: 100vh - 搭配
display: flex; flex-direction: column;,再用margin-top: auto把底部导航推到底部(不依赖position: fixed) - 必须加
<meta name="viewport" content="viewport-fit=cover">,否则 Safari 不触发 dvh 动态更新 - 用
@supports (min-height: 100dvh)包裹规则,老浏览器自动 fallback 到min-height: 100vh
遇到微信内置浏览器或旧版 Safari 怎么办
微信浏览器至今不支持 dvh,iOS 15.x 及更早也无此能力。此时不能只靠 CSS:
- 监听
focusin到<input>或<textarea></textarea>,用setTimeout(() => {}, 0)确保 DOM 重排完成后再读window.innerHeight - 计算当前可用高度后,给底部容器加临时 class,切换为
position: absolute并设bottom: 0(注意:不是bottom: env(keyboard-inset-bottom),微信不支持) -
blur后立刻清除内联样式,并恢复position: relative或 flex 布局行为 - 关键防御:每次操作前校验
document.activeElement === targetInput,避免焦点异步转移导致错位
千万别混用 svh/lvh 和 dvh
svh 是最小可能视口(地址栏展开时),lvh 是最大可能视口(地址栏收起后),而 dvh 是当前真实可视高度。三者语义不同,不能互换:
- 写
height: 100dvh是为了“贴合此刻用户看到的屏幕”,不是为了“撑满所有可能状态” -
env(keyboard-inset-bottom)是偏移量(单位 px),100dvh是长度单位(% of current height),二者不能出现在同一calc()表达式里 - 部分安卓 WebView(如华为 EMUI 内置)对
dvh支持不稳定,需实测;若发现高度跳变,优先降级为 JS +visualViewportAPI 监听
实际最易被忽略的一点:哪怕用了 100dvh,如果父元素设置了 transform、perspective 或 will-change,iOS 仍会破坏 fixed 定位逻辑——这种隐式层叠上下文干扰,比单位选错更难排查。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











