Appearance
脱离标准流:三种方式,三种不同的布局路径
“脱离标准流”不是单一行为。float、absolute、fixed 虽然都被归到“脱离流”这一类描述中,但在浏览器布局引擎中的处理路径并不相同。
标准流与脱离流的分界
在 CSS 规范中,元素分为两类:
- In-flow 元素:参与父容器的 BFC 高度计算,在 normal flow 中子元素按顺序占据空间、互相推开
- Out-of-flow 元素:被移出 normal flow,不参与父容器高度计算,其他兄弟元素忽略它的存在
脱离流的三种方式:
| 方式 | 在 LayoutBox 树中 | 影响兄弟元素布局 | 影响父容器高度 |
|---|---|---|---|
float: left/right | 记录为 overhanging float | 行框环绕(仅文本和内联元素) | ❌ |
position: absolute | 记录为 positioned object | ❌ 完全不影响 | ❌ |
position: fixed | 同 absolute | ❌ 完全不影响 | ❌ |
关键点:脱离流不是从渲染树中消失——元素仍然在 LayoutBox 树中存在,只是不参与 normal flow 的空间分配。
float:部分脱离,保留行框影响
float 是"保留行框影响"的脱离方式。在 Blink 引擎中,float 元素在 LayoutBlockFlow::AddOverhangingFloats 中被标记为 overhanging,不参与父容器高度累加。但它保留了一项能力:后续 in-flow 的文本和内联元素会环绕它。
html
<div class="parent">
<img style="float: left; width: 100px; height: 100px;" />
<p>这段文字会环绕图片排列,而不是像 absolute 那样被图片覆盖。</p>
</div>float 的布局约束:
- 被限制在父容器的 content-box 内(左浮动不超过左边界,右浮动不超过右边界)
- 当前行没有足够空间容纳 float 时,它会被挤到下一行
- 多个同向 float 会在一行内依次排列
clear: left/right/both可以阻止后续元素的环绕行为
float 的设计初衷就是图文混排(文字环绕图片),不是页面布局。把它用于多列布局是历史上没有更好工具时的 hack。
absolute:完全脱离,relative 定位祖先
position: absolute 的元素完全从 normal flow 中移除——它既不影响也不被任何 in-flow 元素影响。相对于最近的定位祖先(position 不为 static 的元素)的 padding-box 定位。
absolute 定位的包含块查找逻辑:
text
1. 沿父级链向上查找
2. 第一个 position !== static 的元素 → 以其 padding-box 为包含块
3. 如果到 <html> 都没有 → 包含块为初始包含块(ICB,通常等于视口)实际工程里最常见的模式是:position: relative 的容器配合 position: absolute 的子元素。relative 主要用于创建包含块,自身不会移动,除非显式设置 top/left。
absolute 元素的尺寸默认由内容撑开(shrink-to-fit)。当你设置 left: 0; right: 0 时,它会在包含块内拉伸填充——这是实现覆盖层(overlay)的标准做法。
fixed:视口绑定与"失效"的根因
position: fixed 和 absolute 共用相同的定位计算路径(Blink 的 LayoutOutOfFlowPositioned),但包含块固定为视口。
fixed 定位“失效”的高频原因:
祖先元素设置了以下 CSS 属性之一:
css
.ancestor {
transform: translateZ(0); /* 创建新的包含块 */
/* 或 */
filter: blur(0); /* 同上 */
/* 或 */
perspective: 1000px; /* 同上 */
/* 或 */
will-change: transform; /* 同上 */
/* 或 */
contain: paint; /* 同上 */
}CSS Transforms 规范规定:transform 值不为 none 的元素为其后代 fixed 元素创建新的包含块。这意味着 fixed 元素变成相对于有这个属性的祖先定位,而不是相对于视口——视觉上它随页面滚动移动,看起来像"fixed 没生效"。
这是 CSS 规范有意为之的行为,不是浏览器 bug。规范的理由是:transform 创建了一个新的坐标空间,fixed 元素如果相对于视口定位,就跳出了这个坐标空间——这在数学上不一致。
排查方法:DevTools → Elements → 沿父级链逐级检查 Computed 面板中的 transform、filter、perspective、will-change、contain 属性,找到第一个值不默认的元素。
relative:不脱离流
position: relative 不脱离流——元素保留其在 normal flow 中的空间,其他元素相对于它原本的位置计算。top/left 的偏移纯粹是视觉上的——布局计算使用原始位置,视觉渲染使用偏移位置。
这解释了为什么 relative 可以被用来微调图标位置,但不会导致兄弟元素重新排列。
实际应用决策
- 图文混排(文字环绕) →
float:这是它唯一正确的用途 - 页面级布局(栅格、等高列) →
flex或grid:float 布局是历史遗留 hack - 覆盖层(modal、tooltip、dropdown) →
position: absolute+ relative 父容器 - 固定导航栏/悬浮按钮 →
position: fixed,注意祖先元素没有 transform/filter - 微调元素位置(对齐图标等) →
position: relative
