vw 按布局视口宽度(含滚动条)计算,即 window.innerWidth,而非扣除滚动条后的 clientWidth;因此 100vw 在有滚动条时会溢出,导致水平滚动条或错位。

vw 是按什么宽度计算的?
vw 单位定义为视口(viewport)宽度的 1%,但它不等于浏览器窗口外框宽度,也不等于 document.documentElement.clientWidth。它基于「初始包含块(initial containing block)」的宽度,而这个块在大多数浏览器中等同于「布局视口(layout viewport)宽度」——即滚动条未被扣除的宽度。
这意味着:当页面内容触发垂直滚动条时,滚动条会占据一部分可用水平空间(比如 17px),但 vw 依然按「整个窗口内边框宽度」计算,而不是减去滚动条后的可视区域宽度。
典型表现:width: 100vw 的元素在有滚动条时会略微溢出,导致水平滚动条二次出现,或与 100% 宽度元素明显错位。
如何验证 vw 是否包含滚动条宽度?
用这段代码快速比对:
立即学习“前端免费学习笔记(深入)”;
<style>
.test-100 { width: 100%; background: #eee; }
.test-100vw { width: 100vw; background: #ffeb3b; }
</style>
<div class="test-100">100%</div>
<div class="test-100vw">100vw</div>
在内容足够长、触发右侧滚动条后,你会发现 .test-100vw 比 .test-100 更宽——差值基本等于滚动条宽度(Windows 常为 17px,macOS 默认隐藏但强制显示时约 15px)。
替代方案:什么时候该用 100% 而不是 100vw?
100% 是相对于父容器宽度计算的,而父容器(如 body 或 html)默认宽度受滚动条影响较小(尤其在 overflow-x: hidden 未启用时)。因此:
- 做全屏横幅、页脚贴底等需严格对齐内容区域的场景,优先用
100%+box-sizing: border-box - 需要真正撑满“窗口像素宽度”(比如背景渐变、Canvas 全屏渲染),才考虑
100vw,但必须手动补偿滚动条 - 若依赖 JS 动态设置宽度,可用
document.documentElement.clientWidth获取真实可用宽度,它已扣除滚动条
滚动条宽度不固定,怎么安全补偿?
没有跨平台统一值,但可运行时读取:
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; console.log(scrollbarWidth); // 通常 0(无滚动条)、17(Win)、15(macOS 显示时)
然后在 CSS 中动态注入(例如通过 style 标签或 CSS 自定义属性):
document.documentElement.style.setProperty('--scrollbar-w', `${scrollbarWidth}px`);
再在样式中使用:
.full-width-safe {
width: calc(100vw - var(--scrollbar-w));
}
注意:这个值只在滚动条状态变化时才需重算,比如监听 resize 或 DOMContentUpdated 并配合 setTimeout 防抖更稳妥。
真实项目里最容易被忽略的,是假设所有用户环境滚动条宽度一致,或者在 CSS 预处理阶段硬编码 calc(100vw - 17px)——一旦 macOS 用户禁用“自动隐藏滚动条”,或 Linux 用户换主题,就立刻失效。


















