> For the complete documentation index, see [llms.txt](https://lizh.gitbook.io/knowledge/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://lizh.gitbook.io/knowledge/css-xi-lie/04css-tan-suo-xi-lie-margin.md).

# CSS探索系列 Margin

**外边距（margin）位于元素边框外侧**，用来分隔当前盒子与周边其他盒子。其他盒子不会侵入外边距区域，父元素背景可以透过外边距区域显示出来。

**作用**：控制盒子与周边元素的间距，实现留白、居中；同时具备几个特殊行为：块级元素会发生**外边距折叠**、百分比取值参照包含块行内尺寸、`auto` 关键字分配剩余空间。

## 取值与简写

`margin` 可拆分为四个独立方向属性：`margin-top`、`margin-right`、`margin-bottom`、`margin-left`；也支持复合简写。

可用取值：

* **长度值**：`px`、`rem`、`em` 等；支持负值，负值会让相邻元素互相靠近，甚至发生重叠。
* **百分比**：相对于包含块的**行内尺寸**计算。
* **auto**：行内方向可自动分配剩余空间；普通流下块向会解析为 `0`。

**简写规则**：顺序「上 → 右 → 下 → 左」，支持 1～4 个值自动推导补全剩余方向。

## 外边距折叠

**外边距折叠（margin collapsing）**：同一\*\*块级格式化上下文（BFC）\*\*中，普通流块级盒子会发生垂直外边距合并，最终只保留一个外边距。

水平书写模式下，仅 `margin-top`、`margin-bottom` 参与折叠；竖排书写模式，折叠行为切换到行方向。

**合并计算规则**：

* 两边均为正值：取更大的值；
* 一正一负：两值直接相加；
* 两边均为负值：取更负（绝对值更大）的值。

**折叠必要条件**：

* 参与折叠的是**普通流块级盒子**；
* 两个外边距之间无分隔：不存在 `border`、`padding`、内容、行框阻断；
* 不参与折叠：浮动元素、绝对定位元素、Flex子项、Grid子项。

**四类折叠场景**：

* **父子顶部折叠**：父 `margin-top` 与第一个普通流子元素 `margin-top` 合并；
* **兄弟相邻折叠**：前一个兄弟的 `margin-bottom` 与后一个兄弟的 `margin-top` 合并；
* **父子底部折叠**：高度为 `auto` 的父容器 `margin-bottom`，与最后一个普通流子元素 `margin-bottom` 合并；
* **空盒子自折叠**：盒子满足 `height:0|auto`、`min-height:0`、无普通流子元素、没有新建 BFC，自身 `margin-top` 和 `margin-bottom` 互相合并。

**示例**：两个相邻段落设置 `margin:50px 0`，中间间距不是 `50+50=100px`，而是折叠后 `50px`。

```html
<div style="border:1px solid #f00;">
    <p style="margin:50px 0;">我喜欢回忆，是因为回忆是我们审视自己生活的过滤器。</p>
    <p style="margin:50px 0;">回忆模糊不清，就给自我欺骗提供了机会。</p>
</div>
```

**阻止外边距折叠的手段**：

* 根元素的 `margin` 不会参与折叠；
* 创建新 BFC，阻断**父子**折叠（兄弟仍在同一父级 BFC 中）；
* 父容器增加 `border`、`padding`，隔断父子 `margin` 接触；
* 父容器设置非 `auto` 的 `height`，避免和最后子元素折叠；`min-height`、`max-height` 挡不住这一类；
* 空盒子设置 `height`、`min-height`，阻止自身上下 `margin` 折叠。

## margin 百分比

所有方向的 `margin` 百分比，统一参照**包含块的行内尺寸**：

* 默认横排（水平书写）：行内尺寸 = 包含块宽度；哪怕是 `margin-top`、`margin-bottom`，百分比依旧参照宽度，**不是父容器高度**。
* 竖排书写模式：行内尺寸 = 包含块高度。

**设计原因**：主要为规避普通流下的循环布局依赖；统一全部方向参照行内尺寸是规范方案。

**示例**：父容器宽 `600px`、高 `300px`；子元素设置 `margin:10% 5%`。

**计算结果**：上下margin = `600*10% = 60px`，左右margin = `600*5% =30px` → 最终 `margin:60px 30px 60px 30px`，**不是参照父高度得到 15px**。

```html
<div style="width:600px;height:300px;border:1px solid #f00;">
    <p style="margin:10% 5%;background:#0aa;">
        我喜欢回忆，是因为回忆是我们审视自己生活的过滤器。
    </p>
</div>
```

## margin 的 auto

普通流块级盒子：**块向（横排下即上下）的 `margin-top:auto`、`margin-bottom:auto` 计算值为 0**，`margin: auto` 等价于 `margin:0 auto`，仅实现水平居中。该规则对绝对定位、Flex、Grid子项不生效。

行内方向（横排下左右）的 `auto` 会分配剩余空间。

**规则总结**：

* 普通流块盒子必须设置确定宽度，左右 `auto` 才有剩余空间可分配；`width: auto` 时盒子直接撑满父容器，无余量。
* 非替换行内盒子，左右 `auto` 解析为0，无法用 `margin:0 auto` 居中。
* **绝对定位**：对应轴的两个 inset 都设置（水平居中要 `left` 与 `right`）且该轴有确定尺寸，`margin:auto` 可在该轴居中。
* Flex 子项、Grid子项：四个方向 `auto` 都可以瓜分剩余空间；单个子元素下写 `margin: auto` 可实现完全居中。

**示例**：父宽 `600px`，子定宽 `200px`，设置 `margin-left:auto`。剩余空间全部分配给左侧，实现右对齐。 ![image-20200503234104813](https://my-files-1259410276.cos.ap-chengdu.myqcloud.com/md_images/image-20200503234104813.png)

```html
<div style="width:600px; border:1px solid #f00;">
    <div style="width:200px; margin-left:auto; background:#0aa;">
        我喜欢回忆，是因为回忆是我们审视自己生活的过滤器。回忆模糊不清，就给自我欺骗提供了机会。—— 石黑一雄《远山淡影》
    </div>
</div>
```

## margin 与相对定位偏移的区别

`margin-top: 30px` 和 `position:relative; top: 30px` 视觉上都可以让盒子向下偏移 `30px`，但布局行为完全不同：

* **margin**：参与普通流布局。会改变盒子占位，后面兄弟元素跟随偏移后的位置继续排布。
* **relative + top**：只改变盒子渲染绘制位置。元素原本占位保持不变，后续兄弟依旧在原始位置排布。

![image-20260817234034516](https://my-files-1259410276.cos.ap-chengdu.myqcloud.com/md_images/image-20260817234034516.png)

![image-20260817234110357](https://my-files-1259410276.cos.ap-chengdu.myqcloud.com/md_images/image-20260817234110357.png)

```css
section { padding: 20px; border: 1px solid #f00; }
.item { display: inline-block; width: 48%; vertical-align: top; }
```

```html
<section>
    <div class="item">
        <div style=" margin-top: 30px; color: #a0a; border: 1px solid #a0a;">
            我喜欢回忆，是因为回忆是我们审视自己生活的过滤器。回忆模糊不清，就给自我欺骗提供了机会。—— 石黑一雄《远山淡影》
        </div>
        人常常靠着改编过的过去获得心灵平静。
    </div>
    <div class="item">
        <div style="position: relative; top: 30px; color: #0aa; border: 1px solid #0aa;">
            我喜欢回忆，是因为回忆是我们审视自己生活的过滤器。回忆模糊不清，就给自我欺骗提供了机会。—— 石黑一雄《远山淡影》
        </div>
        人常常靠着改编过的过去获得心灵平静。
    </div>
</section>
```

## 书写模式对 margin 的影响

`margin` 的折叠、百分比、`auto` 逻辑，跟随**行向、块向**，而不是固定的上下左右物理方向。

* **水平书写 `horizontal-tb`（默认）**
  * 折叠发生在垂直物理方向；
  * 百分比参照包含块宽度；
  * 块向（上下）`auto` = 0；`margin:auto` 等价 `margin:0 auto`。
* **垂直书写 `vertical-rl`、`vertical-lr`**
  * 折叠发生在水平物理方向；
  * 百分比参照包含块高度（此时高度为行内尺寸）；
  * 块向（物理左右）`auto` = 0；`margin:auto` 等价 `margin:auto 0`。

```css
.h { writing-mode: horizontal-tb; }
.v { writing-mode: vertical-rl; }
```

## 圣杯布局

**圣杯布局**：两侧定宽、中间自适应。

**实现**：中间 DOM 靠前优先渲染；全部浮动；利用负 `margin` 把侧边栏拉回可视区域，再用 `relative` 偏移到父容器 `padding` 预留的位置。

![image-20260817234904408](https://my-files-1259410276.cos.ap-chengdu.myqcloud.com/md_images/image-20260817234904408.png)

```html
<section>
    <header>header</header>
    <div class="container">
        <div class="middle">middle（中间弹性区）</div>
        <div class="left">left</div>
        <div class="right">right</div>
    </div>
    <footer>footer</footer>
</section>
<style>

    header, footer, .middle, .left, .right {
        border: 1px solid #0aa;
        padding: 15px 10px;
        text-align: center;
        box-sizing: border-box;
    }

    .container {
        overflow: hidden;
        padding: 0 200px;
    }

    .middle {
        width: 100%;
        float: left;
    }

    .left {
        width: 200px;
        float: left;
        margin-left: -100%;
        position: relative;
        left: -200px;
    }

    .right {
        width: 200px;
        float: left;
        margin-left: -200px;
        position: relative;
        right: -200px;
    }
</style>
```

## 双飞翼布局

**双飞翼布局**：两侧定宽、中间自适应。

**实现**：不使用 `relative` 偏移，中间栏内部嵌套一层，靠内层 `padding` 留出侧边栏位置；侧边栏仅使用负 `margin` 完成定位。

```html
<section>
    <header>header</header>
    <div class="container">
        <div class="middle">
            <div class="in">middle（中间弹性区）</div>
        </div>
        <div class="left">left</div>
        <div class="right">right</div>
    </div>
    <footer>footer</footer>
</section>
<style>
    header, footer, .middle, .left, .right {
        padding: 15px 10px;
        border: 1px solid #0aa;
        box-sizing: border-box;
        text-align: center;
    }

    .container {
        overflow: hidden;
    }

    .middle {
        width: 100%;
        float: left;
    }

    .in {
        padding: 0 200px;
    }

    .left {
        width: 200px;
        float: left;
        margin-left: -100%;
    }

    .right {
        width: 200px;
        float: left;
        margin-left: -200px;
    }
</style>
```

## margin 视觉无效的几种场景

* **外边距折叠**：外边距发生合并，修改数值较小一侧，视觉看不出变化。
* **行内非替换元素**：垂直 `margin` 不参与行高计算，视觉无效；水平 `margin` 正常生效。替换元素，如：`<img>` 垂直 `margin` 有效，也不参与折叠。
* **表格盒模型**：`tr`、`td`、`display:table-row`、`table-cell` 不支持 `margin`；`table`、`inline-table`、`table-caption` 可以正常使用 `margin`。
* **绝对定位元素**：没有全部设置对应方向 `inset` 时，对应方向 `margin` 不参与剩余空间分配。四个方向 `inset` 都设置且盒子有确定宽高，`margin:auto` 才可以完全居中。
* **父容器定高、定宽**：父设置固定高度，不会被子元素 `margin-bottom` 撑开；定宽同理，`margin-right` 撑不开父宽度。`margin` 会溢出父容器，看起来像失效，实际计算存在。

## 小结

`margin` 控制盒子与周边元素的布局关系：

* 普通流块级盒子会发生外边距折叠；
* 百分比统一参照包含块行内尺寸，受书写模式影响；
* 普通流下块向 `auto` 解析为0；
* 需要推动周边盒子位置使用 `margin`；只想移动自身、不影响后续布局，选择相对定位偏移。
