100vh 是手机浏览器底部工具栏抖动的根源之一,因其静态特性无法响应工具栏显隐导致视口变化,与动画叠加引发视觉撕裂;应改用带 fallback 的 100dvh,并避免绝对定位元素直接依赖 vh 单位。

100vh 不是解决手机浏览器底部工具栏抖动的方案,而是抖动的根源之一——它和 CSS 动画叠加后会放大视觉错位,尤其在 Safari 和部分安卓 WebView 中。
为什么用 100vh + 动画会让工具栏抖动更明显
100vh 是静态单位,计算值在页面加载时就锁死。当用户滚动触发地址栏/底部工具栏显隐时:
- 实际可视高度变化(例如 +80px),但
100vh值不变 - 若动画作用于一个
height: 100vh的容器,其尺寸未重排,而内部元素按新视口位置渲染 → 视觉上出现“撕裂”或“瞬移” - 在 Safari 中,
transform动画配合100vh容器还可能触发 layout viewport 重绘,加剧抖动
常见现象包括:轮播图切换时底部突然露白、@keyframes 中 top/bottom 位移错帧、固定按钮在动画中途被工具栏覆盖。
应该用 100dvh 替代 100vh,但必须加降级
100dvh 才是真正响应工具栏变化的动态单位,它会随 visualViewport.height 实时更新,和动画天然兼容。
立即学习“前端免费学习笔记(深入)”;
不过不能裸写:
- iOS 15.x 及更早版本 Safari 完全不识别
100dvh,整条规则被丢弃 - 某些 Android WebView(如旧版 Cordova)也忽略该单位
正确写法是叠加 fallback + @supports 包裹:
.animated-section {
height: 100vh;
}
@supports (height: 100dvh) {
.animated-section {
height: 100dvh;
}
}注意:优先用 min-height 而非 height,避免内容超长时被截断;若动画依赖精确高度(如 scaleY 或 clip-path),更要确保父容器高度可伸缩。
绝对定位动画元素别依赖 vh 类单位算高
如果动画目标是 position: absolute 元素,且你写了类似:
.slide-in {
top: 0;
height: 100vh; /* ❌ 危险 */
animation: slideUp 0.3s;
}那它实际高度仍取决于包含块(containing block)——而这个块很可能没设高度,导致 100vh 失效或行为不可控。
更稳的做法是:
- 改用
top: 0; bottom: 0;直接撑满(不依赖 height 计算) - 或确保其最近的
position: relative祖先有height: 100dvh(并带 fallback) - 若必须用
height,改用calc(100dvh - env(safe-area-inset-bottom))避开刘海/导航栏遮挡(仅限支持env()的环境)
JS 动画兜底时,别在 resize 里直接改 style.height
有些项目仍用 JS 控制动画容器高度(比如基于 scroll 或 visualViewport 的入场效果)。这时容易踩坑:
- 移动端
resize触发极频繁(尤其 Safari 滚动时),直接赋值el.style.height = window.innerHeight + 'px'会引发强制同步布局(layout thrashing) -
getComputedStyle(el).height返回的仍是初始100vh值,无法反映当前真实像素
推荐做法:
- 用
requestAnimationFrame节流,每帧最多更新一次 - 把高度存为 CSS 自定义属性:
document.documentElement.style.setProperty('--dvh', visualViewport?.height + 'px') - 动画中通过
height: calc(var(--dvh, 100vh))引用,保持声明式逻辑
真正容易被忽略的是:动画开始前,--dvh 可能还没初始化,首帧高度错误。务必在 DOM ready 后立即执行一次同步。


















