页面从短变长,版心就抖15像素——今天这件事被一行CSS彻底修了

页面从短变长,版心就抖15像素——今天这件事被一行CSS彻底修了

你有没有遇到过这种情况:从一个内容少的页面点进一个内容多的页面,整个版心突然往左跳了十几像素?

用户的第一反应是”网速慢页面在闪”,但实际上这口锅跟网速没关系——是浏览器在给你预留滚动条的空间。

问题到底出在哪

浏览器计算两种宽度:100vw 是包含滚动条的总视口宽度,而 100% 是扣掉滚动条之后的可用宽度。两者差的那一块,刚好就是滚动条的宽度——Windows 上大约 15px,macOS “总是显示滚动条”模式下大约 17px。

没有滚动条的时候,100%100vw 视觉上没区别,因为没有滚动条占位。一旦内容溢出、滚动条出现,浏览器把原本给滚动条留的那块空间从内容区抽走,整个布局就往左缩。页面间跳转的时候尤其明显:首页内容少没有滚动条,内页内容多有了滚动条,版心就抖一下。

SPA 里这个问题更频繁。用户展开一个折叠面板、加载一批数据,滚动条突然出现,整个页面就跳了。这种跳动会被算进 CLS(Cumulative Layout Shift),Google 的 Core Web Vitals 扣分项。

以前的解法都有坑

最早的做法是给 html 加 overflow-y: scroll——这样不管需不需要滚动条,那个空间总是留着。但副作用也很明显:没有内容溢出的时候,滚动条依然显示,整站视觉上就多了一条灰色滚动条,很丑。

后来有人用媒体查询配合 pointer 媒体类型来区分-overlay 还是 classic 滚动条,但写法复杂,兼容性也一般。更多的人干脆放弃治疗,等用户自己适应。

scrollbar-gutter: stable,一行解决

CSS Scrollbars Module Level 3 里的 scrollbar-gutter 就是为这个场景设计的。给它设成 stable,浏览器就会提前给滚动条留好空间,不管滚动条当前在不在。

html {
  scrollbar-gutter: stable;
}

这行代码的逻辑很简单:内容没溢出就没有滚动条,浏览器也不预留 gutter;内容溢出了有滚动条,gutter 空间早就留好了,内容永远不会因为滚动条的出现而被挤走。

scrollbar-gutter 还有另一个值 stable both-edges:在左、右两条边同时预留 gutter 空间,适用于内容居中的场景,保证无论有没有滚动条,内容始终水平居中。

html {
  scrollbar-gutter: stable both-edges;
}

渐进增强的最优写法

scrollbar-gutter 的降级行为对开发者很友好:不支持的浏览器会直接忽略这个属性,页面保持原样——既不会坏,也不会出现双重 gutter。

如果你想更保守一点,可以配合 @supports 做渐进增强:

html {
  overflow-y: scroll; /* 兜底:旧浏览器总是留空间 */
}
@supports (scrollbar-gutter: stable) {
  html {
    overflow-y: auto; /* 支持 scrollbar-gutter 的浏览器动态控制 */
    scrollbar-gutter: stable;
  }
}

这样做的效果是:旧浏览器靠 overflow-y: scroll 硬留空间(丑但稳),新浏览器用 scrollbar-gutter: stable 精确控制(美且稳)。

三个必须用它的场景

首页跳内页。 内容少的落地页没有滚动条,内页内容多有滚动条,一跳版心就抖。SPA 路由切换同理。用 scrollbar-gutter: stable 一劳永逸。

展开/折叠类交互。 折叠面板打开、筛选器展开、数据异步加载——这些操作都可能让滚动条突然出现或消失,触发布局抖动。给根元素加这行代码,这些场景都不用单独处理。

配合 View Transitions。 Chrome 的 Cross-document View Transitions 让 MPA 页面间有过渡动画。但如果跳转前后滚动条状态不一致,过渡动画会因为版心跳了 15px 而显得很廉价。scrollbar-gutter 把这个变量抹掉,View Transitions 的动画才真正干净。

注意两个坑

不要给所有 overflow 元素都加。 scrollbar-gutter 只影响经典滚动条(占布局空间的那种),overlay 滚动条(macOS/iOS 默认,浮在内容上方)不受影响。如果你的设计里有些元素本身就是用滚动条做视觉装饰的,给它们加 scrollbar-gutter: stable 会导致没有滚动时也出现空白 gutter,反而丑。

RTL 要注意方向。 stable 预留的是 inline-end 方向的 gutter,对 LTR 页面来说是右边,对 RTL 页面来说是左边。如果你的站点同时有中英文两种布局,需要在 RTL 下额外处理,或者直接用 stable both-edges 确保两边对称。

下一步

一行代码检查你的站点:在控制台里切一个内容少的页面和一个内容多的页面,观察右侧版心位置有没有跳。有跳就把这行加进去:

html {
  scrollbar-gutter: stable;
}

如果你用了 Tailwind CSS,scrollbar-gutter 已经被映射成 Tailwind 官方插件,直接用 scrollbar-gutter-stable 这个 class 就行。


这个问题从 Web 诞生就有,到现在二十多年了。浏览器的宽度计算逻辑没变,但 CSS 终于给了一个干净的原生解法——不需要 JS 监听,不需要媒体查询,一行 CSS 让版心从今以后稳稳当当。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 9 阅读