在 CSS 的上古时代,Flexbox 和 Grid 这些现代布局利器还未诞生,前端工程师们为了把块级元素(比如 <li> 或者 <div>)横向排列,最主要的手段就是使用 浮动(float)。
然而,凡是有浮动的地方,几乎都会伴随着一个让人抓狂的幽灵 Bug:父元素高度塌陷。
为了解决这个问题,一代又一代的前端人总结出了一套名为 clearfix(清除浮动)的内功心法。虽然今天纯粹为了布局去使用 float 的场景已经非常罕见了,但理解这段历史以及背后的 BFC 机制,依然是每一位合格前端的必修课。
在常规的文档流中,一个父容器的高度,是由它内部的子元素自然撑开的(假设你没有写死固定高度)。
但是,当你给子元素设置了 float: left 或者 float: right 后,这些子元素就会“起飞”,脱离了原本的普通文档流。
这就导致了一个灾难性的后果:父盒子环顾四周,发现(在最初的文档流层面上)自己肚子里空空如也,于是它的高度瞬间变成了 0。
父盒子这么一瘪,原本紧跟在父盒子里面的后面的内容,或者跟在父盒子外面的兄弟元素,全都会挤上来,页面布局瞬间乱作一团。
这就是著名的“浮动高度塌陷”。
为了抵御这种塌陷,业界总结出了多种应对方案,其中经受了岁月考验并被奉为圭臬的,就是利用伪元素实现的 .clearfix 方案。
当年几乎每一个经过考验的前端框架(例如早期的 Bootstrap ),底层都会有一段这样的 CSS 代码:
clear: both的核心作用是:强行让当前元素移动到它前面所有浮动元素(不管是左浮动还是右浮动)的下方。
这段代码就像是在那些脱缰起飞的浮动子元素之后,悄悄派出了一个看不见、也没有高度的“压舱石”(::after伪元素)。为什么这个压舱石能够分毫不差地把父元素撑回到原来应有的高度?
核心逻辑在于浏览器计算父元素高度的规则与 clear: both 的物理位置相互碰撞:
display: block)。clear: both 属性,它遵循一条铁律——我的左右两边绝对不允许被任何浮动元素遮挡。如果上面的浮动子元素参差不齐(比如左边高 500px,右边高 200px),压舱石为了避开所有的浮动元素,只能委屈自己,硬生生地被迫下沉到最高的那个浮动元素(500px)的正下方。500px 的深度。为了包裹住它,父盒子只好乖乖把自己的高度向下拉伸到了 500px。至于会不会出现部分塌陷?绝对不会。因为只要有了 clear: both,压舱石就肯定会被强行推挤到所有浮动元素的绝对最下方。而我们的代码又给伪元素写了 height: 0;、content: "";,说明这块压舱石不占据任何垂直体积,纯粹只是一个“占位底线坐标”。最终父元素的底部恰好就贴合到了浮动元素的底部,毫厘不差,完美闭合。
除了强行塞一个伪元素去“闭合”浮动之外,CSS 其实还有另一套更为底层的逻辑来包容浮动元素:BFC(Block Formatting Context,块级格式化上下文)。
BFC 是一个独立渲染的区域,它有一个极其重要的特性:计算 BFC 的高度时,浮动元素也会参与计算。 也就是说,只要我们想办法把那个塌陷的父元素触发为 BFC 结界,它就能把起飞的浮动子元素强行罩在里面,高度自然就恢复了。
常见触发父元素 BFC 的手段包括(但不限于):
overflow: hidden:这曾经是最流行的偷懒做法。简单有效,但它有巨大的副作用——如果子元素真的需要超出父容器显示(比如绝对定位的下拉菜单),就会被无情截断。display: flow-root:这是 W3C 在看到了开发者们长期遭受“清除浮动”之苦后,特地在现代 CSS 中加入的一个专门用来触发 BFC 的无副作用属性。Q:什么是浮动塌陷?除了使用伪元素的 clearfix 原理,还有哪些现代手法来清除浮动?
回答思路:
float 后脱离常规文档流,导致未设置固定高度的父元素无法计算内部高度从而收缩为 0,这会引起后续元素的错位。.clearfix::after。核心在于利用伪元素生成一个块级盒子(display: block),并利用 clear: both 属性强行将其自身排布在浮动元素之下,从而利用其身躯撑开父容器。overflow: hidden/auto(会说明其截断内容的副作用)。display: flow-root,它旨在不产生任何额外副作用地创建一块新的 BFC,是现代清除浮动的最佳实践之一。float 已经退化回它最初的设计目标——实现文字环绕图片的效果。因此,全局使用 clearfix 的历史包袱正在逐渐卸下。