再见了 overflow: overlay

随着 Chrome 114 版本发布,overflow: overlay 属性无论是在标准还是实现上都被废弃了。Can I use 也准备更新相关信息。至此,这个横跨 Chrome 15-113 版本的属性终于要说再见了,这给某些情况下的开发带来了一些麻烦,因为 overflow: overlay 实现的效果仍然无法被简单的替代。

overflow: overlay 能实现什么效果?

我们知道 CSS overflow 属性有 auto visible hidden 等属性,大部分开发者非常熟悉他们的区别。如果不做特殊处理,overflow: auto 或者 overflow: visible 会在内容超出容器时显示滚动条,大部分情况下这是合理的,带来的副作用是可能会导致容器宽度发生变化破坏布局,这种破坏在显示文本内容的时候影响不大,在大的容器里面如果存在边距缓冲影响也不大。

但是有些场景影响会非常大,比如我们要在 PC 网页模拟移动端界面的时候,因为移动端的滚动条默认是显示在内容之上的不会破坏布局(Chrome 开启开发者工具选择移动端设备同样是覆盖在内容之上),如果我们使用 overflow: auto 或者 overflow: visible,那么滚动条就会显示在容器的右侧占据一定的空间,这样就会导致容器宽度发生变化破坏布局。

在 Chrome 114 版本之前 overflow: overlay 能够很好的解决这个问题,它会在内容超出容器时显示滚动条,但是不会隐藏超出的内容,而是将超出的内容显示在滚动条之上,再配合自定义滚动条背景颜色修改为透明,基本上能够在 PC 端模拟出移动端滚动条的效果。

如下图所示,这是在 Chrome 114 版本下 overflow: overlay 失效之后用 DOM 模拟的效果,如果是之前版本使用 overflow: overlay 可以简单轻松的实现。可以看到右边的滚动条效果类似于移动端,半透明覆盖在模拟的移动端界面上,不会破坏布局。

picture 1

关于 overflow: overlay 的相关讨论

实际上还是有人和我一样反对移除 overflow: overlay 的,csswg-drafts#6090 记录了相关讨论,在讨论中有人认为 scrollbar-gutter 属性可以替代 overflow: overlay,这显然不成立。还有人认为应该尊重用户对系统的滚动条设置,这显然也不合理,因为在很多场景下开发者就是要自定义自己的滚动条样式和行为用来提升用户体验。

scrollbar-gutter 能实现同样的效果吗?

为什么说 scrollbar-gutter 不能替代 overflow: overlay ?scrollbar-gutter 的效果可以参考 这篇文章,作者提供了一个很好的配图,引用如下:

picture 2

可以看到 scrollbar-gutter 当设置为 stable 时能够解决滚动条出现带来的晃动问题,但是不能够实现 overflow: overlay 的覆盖效果,因为 scrollbar-gutter 实际是通过预留空间来解决晃动问题的,而不是将滚动条覆盖在内容之上。所以 scrollbar-gutter 能够解决滚动条出现带来的布局破坏问题,但是不能够完全替代 overflow: overlay。

overflow: overlay 不支持了应该怎么办

最简单粗暴的是通过 ::-webkit-scrollbar { width: 0; } 直接隐藏滚动条,Vant 在 PC 端的预览界面使用此方案,可以在其 CSS 中找到相关代码。这样能够解决破坏布局的问题,但是无法模拟移动端的滚动条效果。

::-webkit-scrollbar {
  width: 0;
  background: transparent;
}

对于模拟移动端的开发工具来说,我们希望尽可能少的修改 DOM 结构实现,就像 overflow: overlay 可以让相同 DOM 结构同时适配 PC 预览界面和移动端渲染界面,所以需要修改 DOM 结构的方案不是很理想,但还是可以考虑:

比如使用两个容器,然后通过 margin-right 为负值来实现滚动条覆盖在内容之上的效果,但是这个方案会影响 DOM 结构;使用一些第三方库如 react-scrollbars-custom 彻底自定义滚动条,不过这个方案不但会影响 DOM 结构,兼容性和复杂度也会上升,react-scrollbars-custom 的使用可以参考我的示例

最后,我还是选择了修改 DOM 结构的方案模拟 overflow: overlay 的效果,因为在我的场景中 ScrollView 是一个统一的组件,相对好控制一些,具体说来是在 ScrollView 容器下使用绝对定位的滚动条容器实现滚动效果,没有使用两个容器是为了不影响 DOM 层级关系。