先记住这个答案
overflow: auto 至少有四个副作用。第一,元素变成滚动容器,哪怕内容没溢出、没出现滚动条。第二,它创建 BFC,能包住内部浮动,也能阻止和子元素的外边距塌陷。第三,超出它内边距盒的后代会被裁掉,绝对定位的下拉菜单、提示气泡也不例外。第四,内部的 position: sticky 会改为相对它吸顶。另外在 flex、grid 子项上,它还会让最小尺寸从内容宽度变成 0。
- 不管内容溢不溢出,它都已经是滚动容器
- 创建 BFC:包住浮动、阻止父子外边距塌陷
- 溢出的后代会被裁掉,包括绝对定位的弹层
- 内部
sticky改成相对它吸顶
一个侧边栏里同时踩到三个副作用
后台系统的侧边栏常常这样写:固定高度,菜单多了就滚动。加完 overflow: auto 以后,问题接二连三冒出来。
<aside class="sidebar"> <!-- overflow: auto; height: 100vh -->
<h3 class="group-title">系统设置</h3> <!-- position: sticky; top: 0 -->
<div class="menu-item">
用户管理
<ul class="dropdown">…</ul> <!-- position: absolute; left: 100% -->
</div>
</aside>
<style>
.sidebar { height: 100vh; overflow: auto; }
.group-title { position: sticky; top: 0; } /* 现在相对 .sidebar 吸顶 */
.menu-item { position: relative; }
.dropdown { position: absolute; left: 100%; } /* 向右弹出,被 .sidebar 裁掉 */
</style>分组标题的吸顶从「相对页面」变成了「相对侧边栏」,这通常正是想要的效果。但向右弹出的子菜单超出了 .sidebar 的右边界,被整块裁掉了,而且由于侧边栏在横向上也可滚动,还可能多出一条横向滚动条。
子菜单被裁的原因是:它的包含块是 .menu-item,在 .sidebar 里面,所以受 .sidebar 的裁剪。解决办法是把弹层渲染到 body 下(各组件库的 Portal、getPopupContainer 就是干这个的),或者用 position: fixed 按坐标定位。
创建 BFC 带来的变化
BFC 是块级格式化上下文,可以理解成一个独立的布局房间:里面的浮动、外边距不会影响外面,外面的也影响不到里面。
overflow 取 visible 以外的值就会创建 BFC,所以以前清除浮动最常见的写法是给父元素 overflow: hidden,高度就能包住浮动子元素。父元素和第一个子元素的 margin-top 也不会再合并,父元素不会被子元素的外边距「拉下来」。BFC 还不会和外面的浮动元素重叠,常被用来做左边浮动、右边自适应的两栏。
这些效果现在有更干净的写法:display: flow-root 专门用来创建 BFC,没有裁剪和滚动的副作用。只为了 BFC 就写 overflow,是在用一个副作用换另一个副作用。
flex 子项上的隐藏副作用
flex 子项默认 min-width: auto,意思是最小不能比内容还窄。所以一个子项里放了很长的一行文字或一张宽表格,它会把整行撑破。
给这个子项加 overflow: auto,它的自动最小宽度就变成 0,可以正常收缩,内容在内部滚动。很多人把它当成修复手段,但更直接的写法是 min-width: 0,意图清楚,也不会顺带把元素变成滚动容器。
容易答错的地方
- 以为内容不溢出就没有副作用
- 是不是滚动容器看的是
overflow的计算值,跟内容多少无关。没出现滚动条时,BFC、裁剪、sticky参照物这些效果都已经存在。 - 以为只在一个方向写
auto就只影响一个方向 overflow-y: auto配overflow-x: visible时,visible会被计算成auto,两个方向都变成可滚动。想只让纵向滚动、横向不裁剪,这个组合是做不到的。- 以为
auto和scroll只差在滚动条 scroll总是显示滚动条轨道,auto只在溢出时显示,其余副作用两者完全相同。经典滚动条会占用内容宽度,内容一多一少时布局会左右跳动,可以用scrollbar-gutter: stable提前预留位置。
面试官还会怎么问?
只想让横向溢出不显示,又不想影响内部的 sticky,怎么写?
用 overflow-x: clip。clip 只裁剪,不产生滚动容器,内部的 sticky 还是相对更外层的滚动容器生效。而且 clip 配 visible 时,另一方向不会被改成 auto。
绝对定位的子元素一定会被 overflow 裁掉吗?
不一定。要看它的包含块在哪。如果它的包含块在 overflow 元素里面或就是它本身,就会被裁;如果包含块是 overflow 元素外面的某个定位祖先,比如 overflow 元素自己没定位、定位祖先在更外层,它就不受这层裁剪。position: fixed 的元素包含块是视口,一般也不会被裁,除非祖先有 transform。
页面上出现了两条纵向滚动条,一般是什么原因?
通常是 body 或某个包裹层写了 height: 100vh; overflow: auto,内容又比它高,于是它内部滚一套,页面本身也滚一套。先确定页面到底想让谁滚动,只保留一个滚动容器。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。