最简方案是用min-height: 100dvh替代100vh,它能原生响应地址栏收起、键盘弹出等可视区变化,但必须搭配@supports包裹并用min-height而非height,否则旧浏览器失效或内容被裁切。

用 100dvh 替代 100vh 是最简方案(iOS 16.4+ / Chrome 100+)
直接写 min-height: 100dvh 就能响应地址栏收起、键盘弹出等导致的可视区变化,无需 JS 干预。它不是“估算”,而是浏览器原生上报的真实像素值。
但要注意两点:
-
100dvh必须搭配min-height,不能只写height: 100dvh——否则内容超长时会被裁切,滚动失效 - 必须用
@supports (height: 100dvh)包裹,否则旧浏览器会忽略整条规则,连 fallback 都不执行
示例:
body {
min-height: 100vh;
}
@supports (height: 100dvh) {
body {
min-height: 100dvh;
}
}
微信内置浏览器里 100dvh 不可靠,得靠 JS 注入 --vh
微信 iOS ≥ 8.0.49(对应 Safari 16.4+ 内核)才支持 100dvh;安卓版 X5 内核至今多数不识别该单位,@supports 检测会直接跳过,导致 fallback 失效。
实操要点:
- 监听
resize、scroll和微信特有的weixinjsbridgeReady事件 - 用
requestAnimationFrame节流更新,避免安卓下高频 resize 导致卡死 - CSS 中统一用
min-height: calc(var(--vh, 1px) * 100),不依赖父容器是否设了height: 100%
关键代码片段:
const setVh = () => {
const vh = window.innerHeight;
document.documentElement.style.setProperty('--vh', `${vh}px`);
};
if (typeof WeixinJSBridge !== 'undefined') {
WeixinJSBridge.invoke('getNetworkType', {}, setVh);
} else {
window.addEventListener('weixinjsbridgeReady', setVh);
}
window.addEventListener('resize', () => requestAnimationFrame(setVh));
window.addEventListener('scroll', () => requestAnimationFrame(setVh));
老 iOS(iOS 12–15)必须用 visualViewport + orientationchange 双兜底
visualViewport.height 在 iOS 15.4+ 稳定可用,但 iOS 14 及更早版本不支持,此时只能靠 orientationchange 触发后延迟读取 window.innerHeight。
常见错误是只监听 resize 或只在 DOMContentLoaded 里取一次值——Safari 地址栏动画中间态会让 window.innerHeight 返回偏小的值(常少 60–80px)。
正确做法:
- 优先检测
'visualViewport' in window,存在则监听其resize事件 - 否则监听
orientationchange和resize,触发后用setTimeout(() => ..., 100)延迟读取 - 竖屏时可加经验性补偿(如 +60px),但需按机型微调;横屏时直接用
window.innerHeight
别碰 document.documentElement.clientHeight——缩放或 zoom 下它不可靠。
为什么不能只靠 calc(100vh - Xpx) 手动减去地址栏高度
地址栏高度不是固定值:iOS 上从 44px(紧凑型)到 90px(带搜索框)都有可能;安卓各厂商 WebView 行为也不一致;键盘弹出时还会叠加变化。硬编码减法在真实设备上极易错位。
更严重的是,100vh 本身是静态快照,页面加载时取的值不会随后续地址栏隐藏而更新,所以 calc(100vh - 60px) 在滚动后反而比真实可视区还大,造成底部留白。
真正需要的是动态感知能力——要么用浏览器原生的 dvh,要么用 JS 持续监听并注入变量,而不是试图“猜”一个差值。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











