Flex 布局完整指南:告别 float,搞定网页绝大多数弹性布局

0 阅读18分钟

写在前面

你大概被 float 坑过:清浮动要写 clearfix,垂直居中靠 hack,等分靠百分比加负 margin,改一处塌一片。可 float 的初衷只是 "文字环绕图片",本不该承担布局的职责。

Flex 正是为了一维布局而生:沿一个方向排布元素,自动处理对齐、伸缩、换行与顺序,且不依赖容器尺寸,已被所有主流浏览器完整支持多年。本文从核心概念讲到实战踩坑,配合可运行代码,帮你一次性建立完整的 Flex 知识体系。


一、Flex 的核心概念:容器与项目、主轴与交叉轴

理解 Flex,只需要抓住两组对偶概念:容器(container)与项目(item)主轴(main axis)与交叉轴(cross axis)

1.1 容器与项目

对一个元素设置 display: flex,它就成了 flex 容器,它的直接子元素自动成为 flex 项目

<div class="container">   <!-- flex 容器 -->
<div class="item">1</div>  <!-- flex 项目 -->
<div class="item">2\</div>  <!-- flex 项目 -->
<div class="item">3\</div>  <!-- flex 项目 -->
</div>
.container {
display: flex;
}

这里有两个重要限定词:

  • 直接子元素:孙元素不属于 flex 项目,不会被 Flex 规则直接作用。

  • 只沿一条轴:Flex 是一维布局模型,它的核心机制都围绕 "主轴" 展开。二维布局(同时处理行列)应该交给 Grid。

1.2 主轴与交叉轴

Flex 布局中,元素沿 主轴 排布,主轴方向由 flex-direction 决定;垂直于主轴的是 交叉轴

flex-direction: row        主轴 →  交叉轴 ↓(默认)

flex-direction: column     主轴 ↓  交叉轴 →

关键点:主轴方向是可变的,因此 "水平居中" 在 column 容器里就变成了 "垂直方向上的排布问题"。不要用固定的物理思维(左右、上下)去记 Flex 属性,要用 "主轴 / 交叉轴" 这对逻辑概念去理解,否则遇到 column 方向时很容易懵。


二、开启 Flex:display 的两种形态

/* 容器本身占满一行(块级特性) */
display: flex;

/* 容器表现为行内元素,但子元素仍然是 flex 项目 */
display: inline-flex;
<span class="inline-flex-box">   <!-- 多个 inline-flex 可以并排 -->
    <span>a</span>
    <span>b</span>
</span>

<span class="inline-flex-box">
    <span>c</span>
</span>

display: flex 会让容器变成一个 "块级化的 flex 容器";inline-flex 则让容器在外部表现为行内元素(可以和其他行内内容排在同一行),但内部子元素的布局规则完全一致。

设置 display: flex 后,项目默认会发生什么?

  1. 子元素默认横向排成一行(flex-direction: row)。

  2. 子元素默认不换行flex-wrap: nowrap)。

  3. 子元素的 floatvertical-align 等属性失效

  4. 子元素的 margin 不再折叠(正常块级元素相邻 margin 会折叠,flex 项目不会)。

  5. 子元素沿主轴排布,在交叉轴方向默认被拉伸填满(align-items: stretch)。


三、容器属性:布局的 "总开关"

容器上的属性定义了 "项目们如何排布"。共 6 个核心属性:flex-directionflex-wrapjustify-contentalign-itemsalign-contentgap

3.1 flex-direction:决定主轴方向

.container {
    flex-direction: row;            /* 默认,主轴水平,从左到右 */
    flex-direction: row-reverse;    /* 主轴水平,从右到左 */
    flex-direction: column;         /* 主轴垂直,从上到下 */
    flex-direction: column-reverse; /* 主轴垂直,从下到上 */
}
<div class="container" style="flex-direction: row">
    <span>A</span>
    <span>B</span>
    <span>C</span>
</div>

<!-- 渲染:A B C -->
<div class="container" style="flex-direction: column">
    <span>A</span>
    <span>B</span>
    <span>C</span>
</div>
<!-- 渲染:

    A

    B

    C -->

注意:

row-reverse / column-reverse同时反转起始位置 。例如row-reverse下,第一个项目出现在最右侧。但 阅读顺序(DOM 顺序)不变 ,这对无障碍与屏幕阅读器是友好的,应优先用它而不是 order 来实现视觉反转。

3.2 flex-wrap:是否换行

.container {
    flex-wrap: nowrap;  /* 默认,不换行,项目会被压缩 */
    flex-wrap: wrap;    /* 换行,多出来的项目排到下一行 */
    flex-wrap: wrap-reverse; /* 换行,且从下往上排 */
}
<style>
    .box { 
        width: 200px; 
        height: 200px; 
        display: flex; 
        flex-wrap: wrap; 
        }

    .box span { 
        width: 100px; 
        height: 60px; 
        }
</style>

<div class="box">
    <span>1</span>
    <span>2</span>
    <span>3</span>
    <span>4</span>
</div>

<!-- nowrap:4 个元素挤在一行被压缩  -->
<!-- wrap  :每行放 2 个,分 2 行显示 -->

3.3 flex-flow:前两者的简写

.container {
    flex-flow: <flex-direction><flex-wrap>;
}
/* 等价写法 */

.container {
    flex-direction: column;
    flex-wrap: wrap;
}
.container {
    flex-flow: column wrap;
}

3.4 justify-content:主轴方向的对齐

这是最常用的属性之一,决定了项目在主轴上的分布方式。

.container {

    justify-content: flex-start;    /* 默认,靠主轴起点 */

    justify-content: flex-end;      /* 靠主轴终点 */

    justify-content: center;        /* 居中 */

    justify-content: space-between; /* 两端对齐,项目间间距相等 */

    justify-content: space-around;  /* 每个项目两侧间距相等(相邻间距会叠加) */

    justify-content: space-evenly;  /* 所有间距完全相等(含两端) */

}
<style>
     .box { 
         display: flex; 
         width: 400px; 
         }
    .box span { 
        width: 60px; 
        height: 40px; 
        margin: 4px; 
        }

</style>

<!-- space-between:第一个贴左、最后一个贴右、中间等距 -->
<div class="box" style="justify-content: space-between">

    <span>1</span>
    <span>2</span>
    <span>3</span>

</div>

<!-- center:整体居中 -->
<div class="box" style="justify-content: center">
    <span>1</span>
    <span>2</span>
    <span>3</span>
</div>

三者间距计算的区别(容器宽 400,3 个 60px 项目):

取值两端间距项目间间距
space-between0各 110px
space-around各 55px各 110px(两端各算一份)
space-evenly各 73.3px各 73.3px

justify-content只在 主轴有多余空间时才起作用;当项目溢出容器时,它的表现会退化为 "靠起点溢出"。

3.5 align-items:交叉轴方向的对齐

它决定项目在交叉轴上的对齐方式,与 justify-content 正交。

.container {

    align-items: stretch;   /* 默认,拉伸填满交叉轴 */

    align-items: flex-start;/* 靠交叉轴起点 */

    align-items: flex-end;  /* 靠交叉轴终点 */

    align-items: center;    /* 居中 */

    align-items: baseline;  /* 按首行文字基线对齐 */

}
<style>
    .row { 
        display: flex; 
        align-items: center; 
        height: 120px; 
    }
    .row span {
        width: 80px; 
    }

</style>

<!-- 三个高度不同的元素在交叉轴(垂直)方向垂直居中 -->

<div class="row">
    <span style="height: 30px"></span>
    <span style="height: 70px"></span>
    <span style="height: 100px"></span>
</div>

最常用的两个是 center(垂直居中)和 stretch(默认拉伸,让项目填满容器高度)。

经典应用:一行导航里,让图标和文字垂直居中、且不同高度的块都对齐到一条水平线上:

.nav-item {
    display: flex;
    align-items: center;
}

3.6 align-content:多行(多轴)时的整体对齐

align-content 作用于多行场景,即容器发生换行后,对 "每一整行" 在交叉轴方向上进行分布。注意:单行(未换行)时它无效,此时交叉轴对齐由 align-items 负责。

.container {
    flex-wrap: wrap;
    align-content: flex-start;    /* 各行紧贴交叉轴起点 */
    align-content: center;        /* 各行整体居中 */
    align-content: space-between; /* 各行均匀分布 */
    align-content: stretch;       /* 默认,各行拉伸填满 */

}
<style>
    .grid { display: flex; flex-wrap: wrap; align-content: center; height: 300px; }
    .grid span { width: 100px; height: 80px; }
</style>
<!-- 多行项目在容器垂直方向上整体居中,而不是各自拉伸 -->
<div class="grid">
    <span>1</span>
    <span>2</span>
    <span>3</span>
    <span>4</span>
    <span>5</span>
    <span>6</span>
</div>

3.7 gap:项目间距(告别 margin hack)

gap 是近些年才补全的能力,现在已完全可用,可以替代过去 "用 margin + 负值" 的间距方案。

.container {
    gap: 16px;          /* 主轴与交叉轴统一 16px */
    row-gap: 12px;      /* 行间距 */
    column-gap: 20px;   /* 列间距 */
}
<style>
    .cards { display: flex; flex-wrap: wrap; gap: 16px; }
    .cards div { width: calc(33.33% - 11px); height: 80px; }
</style>

<div class="cards">
    <div>卡片1</div>
    <div>卡片2</div>
    <div>卡片3</div>
    <div>卡片4</div>
    <div>卡片5</div>
    <div>卡片6</div>
</div>
<!-- 每张卡片间距统一 16px,无需对首尾元素做 margin 修正 -->

gap 相比 "子元素加 margin" 的最大优势:不会在容器边缘产生多余的间距,不需要再对第一个 / 最后一个子元素做负 margin 或 :first-child 修正。


四、项目属性:单个元素的 "自主权"

容器属性决定集体排布,项目属性则允许单个元素打破默认规则。共 6 个:orderflex-growflex-shrinkflex-basisflexalign-self

4.1 order:控制显示顺序

.item {
    order: 0;   /* 默认值,按 DOM 顺序排列 */
    order: 1;   /* 数值越大越靠后 */
    order: -1;  /* 负数可以排到最前面 */
}
<style>
    .box { display: flex; }
    .item { width: 60px; height: 40px; margin: 4px; }
    .first { order: -1; }
</style>

<div class="box">
    <div class="item">默认1</div>
    <div class="item">默认2</div>
    <div class="item first">提前</div>
</div>
<!-- 渲染顺序:提前 → 默认1 → 默认2(order 相同的按 DOM 顺序) -->

order只改变视觉顺序,不改变 DOM 顺序,也不影响 Tab 键焦点顺序。

做视觉重排用它,做语义顺序调整请修改 DOM

4.2 flex-grow:按比例瓜分剩余空间

flex-grow 决定当主轴有剩余空间时,项目按什么比例放大。

.item {
    flex-grow: 0;  /* 默认,不放大 */
    flex-grow: 1;  /* 占据剩余空间 */
}
<style>
    .box { display: flex; width: 400px; }
    .item { height: 40px; }
    .g1 { flex-grow: 1; }
    .g2 { flex-grow: 2; }
</style>

<div class="box">
    <div class="item g1" style="width:100px">A</div>
    <div class="item g2" style="width:100px">B</div>
</div>
<!-- 剩余空间 400 - 200 = 200px,按 1:2 分配 -->
<!-- A 分得约 66.7px,B 分得约 133.3px -->

flex-grow 分配的是 "剩余空间",而不是按权重重新分配总宽度—— 这是新手最常见的误解。项目自身的 width/flex-basis 仍然占据基准份额。

4.3 flex-shrink:空间不足时的收缩比例

当主轴空间不足时,flex-shrink 决定项目如何缩小。

.item {
    flex-shrink: 1;  /* 默认,空间不足时等比缩小 */
    flex-shrink: 0;  /* 拒绝缩小,可能溢出 */
}
<style>
    .box { display: flex; width: 300px; }
    .item { width: 200px; height: 40px; }
    .noshrink { flex-shrink: 0; }
</style>

<div class="box">
    <div class="item">会缩小</div>
    <div class="item noshrink">不缩小</div>
</div>

<!-- 容器 300px,两个项目各 200px,共 400px -->
<!-- "会缩小"被压缩到 100px,"不缩小"保持 200px 并导致溢出 -->

flex-shrink: 0 在 "不允许某元素被压缩"(如图片、长单词)时非常有用。

4.4 flex-basis:项目的主轴基准尺寸

flex-basis 定义项目在主轴上的初始大小,它优先于 width(在 row 方向)。

.item {
    flex-basis: auto; /* 默认,回退到 width/height */
    flex-basis: 200px;
    flex-basis: 30%;
}

优先级:flex-basis(非 auto)> width > 内容宽度。

<style>
    .box { display: flex; }
    .item { height: 40px; }
</style>

<div class="box">
    <!-- flex-basis 生效,width 被忽略 -->
    <div class="item" style="flex-basis:150px; width:50px">A</div>
    <!-- 没有 flex-basis,width 生效 -->
    <div class="item" style="width:100px">B</div>
</div>

4.5 flex 简写:最常用的速记

.item {
    flex: none;              /* 0 0 auto */
    flex: auto;              /* 1 1 auto */
    flex: 1;                 /* 1 1 0% —— 最常见的"等分"写法 */
    flex: 1 1 200px;         /* grow shrink basis */
}

flex: 1 展开为 flex: 1 1 0%,含义是:基准尺寸为 0,所有剩余空间按 grow 比例分配—— 这正是 "等分剩余空间" 的语义。多个 flex: 1 的兄弟项目会平分可用宽度。

<style>
    .box { display: flex; width: 600px; }
    .item { flex: 1; height: 40px; margin: 0 4px; }
</style>

<div class="box">
    <div class="item">等分1</div>
    <div class="item">等分2</div>
    <div class="item">等分3</div>
</div>
<!-- 三个项目各占约 200px -->

4.6 align-self:单独覆盖容器的 align-items

.item {
    align-self: auto;       /* 默认,继承容器的 align-items */
    align-self: center;
    align-self: flex-end;
    align-self: stretch;
}
<style>
    .box { display: flex; align-items: flex-start; height: 200px; }
    .item { width: 80px; }
    .special { align-self: flex-end; }
</style>

<!-- 其他项目靠上,special 单独靠下 -->
<div class="box">
    <div class="item"></div>
    <div class="item special"></div>
</div>

4.7 margin: auto:Flex 里的 "万能对齐器"

在 Flex 布局中,margin: auto吸收主轴上对应的剩余空间,从而把项目推到指定位置,甚至能做到其他属性做不到的 "双轴居中"。

/* 经典:水平垂直居中,无需指定宽高 */
.container {
    display: flex;
}
.item {
    margin: auto;   /* 主/交叉轴剩余空间全部吸收 → 居中 */
}
<style>
    .full {
        display: flex;
        width: 400px;
        height: 300px;
        border: 1px solid #ccc;
    }
    .ball { width: 60px; height: 60px; margin: auto; }
</style>

<div class="full">
    <div class="ball"></div>
</div>
<!-- 一个圆形被完美地居中在容器中 -->

更妙的是它可以做 "右对齐 + 自动间距":

.toolbar {
    display: flex;
    gap: 12px;
}
.toolbar .right {
    margin-left: auto;  /* 吸收左侧剩余空间 → 被推到最右 */
}
<div class="toolbar">
    <button>首页</button>
    <button>关于</button>
    <button class="right">登录</button>
</div>
<!-- 登录按钮被推到最右侧,无需绝对定位 -->

多个项目同时设margin-left: auto时,剩余空间会在它们之间平分 —— 可以用来实现 "间距均分到任意位置" 的高级效果。


五、实战:8 个高频布局一次写清

理论讲完,进入 "拿来即用" 的实战环节。以下每个案例都给完整可运行的 HTML/CSS。

5.1 水平垂直居中(最高频)

<!DOCTYPE html>
<html>
<head>
<style>
    html, body { height: 100%; margin: 0; }
    .center {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
    }
    .card {
        width: 200px;
        height: 120px;
        background: #f0f4ff;
        border-radius: 8px;
        text-align: center;
        line-height: 120px;
    }
</style>
</head>
<body>
<div class="center">

&#x20;   \<div class="card">我是居中卡片\</div>

&#x20; \</div>

\</body>

\</html>

对比传统方案:margin: 0 auto(只水平)、position + transform(需要知道尺寸)、table 单元格(语义差)。Flex 两行搞定且无需固定尺寸。

5.2 两栏 / 经典圣杯布局


<style>
    .page { display: flex; min-height: 100vh; }
    .sidebar { width: 240px; flex-shrink: 0; background: #f5f5f5; }
    .main { flex: 1; background: #fff; padding: 20px; }
</style>

<div class="page">
    <aside class="sidebar">侧边栏 240px 固定</aside>
    <main class="main">
        主内容区,flex: 1 自动填满剩余宽度
    </main>
</div>

关键点:侧边栏 flex-shrink: 0 保证不被压缩,主内容 flex: 1 吃满剩余空间。若要 "主内容在前、侧栏在右",用 order 或调整 DOM 顺序即可。

5.3 三栏中间自适应(圣杯布局的现代写法)

<style>
    .layout { display: flex; min-height: 100vh; }
    .left, .right { width: 200px; flex-shrink: 0; background: #f5f5f5; }
    .center { flex: 1; background: #fff; }
</style>
<div class="layout">
    <aside class="left">左 200px</aside>
    <main class="center">中间自适应</main>
    <aside class="right">右 200px</aside>
</div>

在 float 时代,这个布局要配合负 margin、calc 甚至 "圣杯 / 双飞翼" 的奇技淫巧;用 Flex 只需 flex: 1 一句话。

5.4 粘性页脚(Sticky Footer):内容不足时页脚依然贴底

<style>
    html, body { height: 100%; margin: 0; }
    .app {
        display: flex;
        flex-direction: column;
        min-height: 100vh;
    }
    .header { 
        background: #333; 
        color: #fff; 
        padding: 16px; 
    }
    .content { flex: 1; padding: 16px; }  /* 关键:内容区吃满剩余空间 */
    .footer { background: #333; color: #fff; padding: 16px; }
</style>

<div class="app">
    <header class="header">头部</header>
    <main class="content">内容(无论多少,页脚都贴底)</main>
    <footer class="footer">页脚</footer>
</div>

原理:容器是 column 方向的 flex,contentflex: 1 吸收所有剩余高度,把页脚顶到底部。内容超出时容器随之变高,页脚自然被推下去 —— 完美兼顾 "少内容贴底" 与 "多内容自然流"。

5.5 导航栏(Logo + 菜单 + 操作区)

<style>
    .navbar {
        display: flex;
        align-items: center;
        padding: 0 20px;
        height: 60px;
        background: #fff;
        box-shadow: 0 1px 4px rgba(0,0,0,.08);
    }
    .logo { 
        font-weight: 700; 
        font-size: 18px; 
    }
    .menu { 
        display: flex; 
        gap: 20px; 
        margin-left: 40px; 
    }
    .actions { 
        margin-left: auto; 
        display: flex; 
        gap: 12px;
    }
</style>

<nav class="navbar">
    <div class="logo">MySite</div>
    <ul class="menu">
        <li>首页</li>
        <li>文档</li>
        <li>定价</li>
    </ul>
    <div class="actions">
        <button>登录</button>
        <button>注册</button>
    </div>
</nav>

margin-left: auto 把操作区推到最右,一行代码省掉了 justify-content: space-between 需要额外加占位元素的麻烦。

5.6 响应式卡片网格(自动换行)

<style>
    .grid {
        display: flex;
        flex-wrap: wrap;
        gap: 16px;
    }
    .card {
        flex: 1 1 240px;      /* 基准 240px,可伸缩 */
        min-width: 0;         /* 防止内容撑破,见踩坑章节 */
        padding: 16px;
        border: 1px solid #eee;
        border-radius: 8px;
    }

</style>

<div class="grid">
    <div class="card">卡片 1</div>
    <div class="card">卡片 2</div>
    <div class="card">卡片 3</div>
    <div class="card">卡片 4</div>
</div>

<!-- 视口宽:放得下几列就放几列,多出的自动换行 -->

flex: 1 1 240px 是 "响应式等宽卡片" 的经典写法:每张卡片至少 240px,空间不足就换行,空间充足就均匀铺开。

5.7 固定头部 + 可滚动内容(移动端常见壳子)

<style>
    html, body { height: 100%; margin: 0; }
    .page {
        display: flex;
        flex-direction: column;
        height: 100vh;           /* 关键:锁定视口高度 */
    }
    .top { 
        flex-shrink: 0; 
        height: 50px; 
        background: #222; 
        color: #fff; 
    }
    .scroll {
        flex: 1;
        overflow-y: auto;        /* 内容区独立滚动 */
    }
    .bottom { 
        flex-shrink: 0; 
        height: 50px; 
        background: #222; 
        color: #fff; 
   }
</style>

<div class="page">
    <div class="top">固定顶栏</div>
    <div class="scroll">
        <p>长内容,可滚动……</p>
        <p>长内容,可滚动……</p>
        <p>长内容,可滚动……</p>
        <p>长内容,可滚动……</p>
    </div>
    <div class="bottom">固定底栏</div>
</div>

flex: 1 让中间区域吃掉所有可用高度,overflow-y: auto 让滚动只发生在内容区,顶栏底栏永不滚动。

5.8 图文对齐 / 列表项

<style>
    .list-item {
        display: flex;
        align-items: center;
        gap: 12px;
        padding: 12px;
        border-bottom: 1px solid #f0f0f0;
    }
    .avatar { 
        width: 40px; 
        height: 40px; 
        border-radius: 50%; 
        background: #4f8cff; 
        }

    .info { flex: 1; }
    .name { font-weight: 600; }
    .desc { color: #999; font-size: 12px; }
    .time { color: #999; font-size: 12px; }
</style>

<div class="list-item">
    <div class="avatar"></div>
    <div class="info">
        <div class="name">张三</div>
        <div class="desc">刚发布了新文章</div>
    </div>
    <div class="time">5 分钟前</div>
\</div>

头像固定、中间信息 flex: 1 吃满、右侧时间靠边 ——"左侧固定 + 中间弹性 + 右侧固定" 是 Flex 最典型的应用模式,几乎覆盖所有列表页。


六、高频踩坑与最佳实践

理论容易,踩坑难免。以下是 Flex 开发中翻车率最高的几个点,建议熟记。

6.1 坑 1:内容撑破布局(min-width: auto)

现象:flex 项目里放了一段长文本或一张大图,即使容器宽度有限,项目也不压缩,把布局撑破。

原因:flex 项目默认 min-width: auto,即 "最小宽度不小于内容宽度",所以内容(如长单词、图片)拒绝被压缩。

解决

.item {
    min-width: 0;      /* row 方向 */
    min-height: 0;     /* column 方向 */
    overflow: hidden;  /* 或 auto,配合内容截断 */
}
<style>
    .box { 
        display: flex; 
        width: 300px; 
    }
    .text { 
        flex: 1; 
        min-width: 0;
        white-space: nowrap; 
        overflow: hidden; 
        text-overflow: ellipsis; 
    }
</style>

<div class="box">
    <span class="text">这是一段非常长的文字,如果不加 min-width:0 它会把布局撑破</span>
    <span>右侧固定</span>
</div>

<!-- 长文本被截断为省略号,而不是撑破容器 -->

经验法则:只要 flex 项目里可能出现 "不换行的长内容"(长英文单词、URL、图片、canvas),就给它补 min-width: 0

6.2 坑 2:flex-basis 与 width 的优先级混淆

记住这条铁律:在主轴方向,flex-basis(非 auto)优先于 widthrow 方向看 flex-basiscolumn 方向看 flex-basis 对应高度。

/* row 方向,flex-basis 覆盖 width */

.item {
    flex-basis: 200px;
    width: 100px;
    } 
    /* 实际 200px */

想清楚 "基准尺寸" 到底是哪个,再决定写 flex-basis 还是 width,不要混用导致预期外结果。

6.3 坑 3:flex: 1 与 min-height 导致的撑开

flex: 1(即 1 1 0%)在 column 方向配合 min-height: auto,子内容多时容器可能被撑高而不是滚动。解决同样是补 min-height: 0,再配合 overflow

6.4 坑 4:垂直居中被 "内容高度" 欺骗

align-items: center 让项目在交叉轴居中,但如果项目自身有 height: 100% 或内容撑满,居中自然看不出来。排查时先确认 "容器高度已确定"(有 height 或由父级传递),否则 flex 容器高度由内容决定,居中无意义。

/* 容器必须有确定高度,align-items 才有居中的"空间" */

.container { 
    display: flex; 
    align-items: center; 
    height: 300px;
    }

6.5 坑 5:flex-wrap 后 justify-content 作用于 "整行" 而非 "全部项目"

换行后,justify-content对每一行分别起作用的。如果希望 "最后一行不满时也左对齐"(如卡片布局最后一行只有 2 个),justify-content: space-between 会把它们拉成两端对齐,观感怪异。常见兜底:改用 gap + 普通排布,或给项目固定宽度。

/* 避免:最后一行只有 2 张卡片时被拉成两端对齐 */

.grid { 
    display: flex; 
    flex-wrap: wrap; 
    justify-content: space-between; 
    }

/* 更稳:固定宽度 + gap,最后一行自然左对齐 */

.grid { 
    display: flex; 
    flex-wrap: wrap; 
    gap: 16px; 
    }

.grid .card { 
    width: calc(33.33% - 11px); 
    }

6.6 坑 6:flex 与 float /position 混用

  • 设置了 display: flex 的容器,其子元素的 float 完全失效 ——不要再用 float + flex 混排。

  • 子元素设 position: absolute 会脱离 flex 布局,不再受 justify-content / align-items 控制(可用绝对定位实现 "覆盖层",但要明确它的脱离语义)。

6.7 最佳实践小结

  1. 优先用 gap 控制间距,不再用 margin hack。

  2. 能用 flex: 1 就不写三连值,语义更清晰。

  3. 有 "不可压缩内容" 就补 min-width: 0 / min-height: 0

  4. 视觉反转优先用 row-reverse / column-reverse,少用 order(可访问性更好)。

  5. align-self 只用于 "个别项目特例",全员不同对齐时优先考虑容器属性是否选错了。

  6. 不确定主轴方向时先画主轴 / 交叉轴示意图,再套属性,别靠记忆猜。


七、Flex vs CSS Grid:到底用哪个?

这是被问得最多的问题。一句话结论:Flex 处理一维(单行 / 单列),Grid 处理二维(行 + 列同时)

维度FlexboxCSS Grid
布局模型一维(沿主轴)二维(行与列)
典型场景导航、居中对齐、列表、等分整页栅格、复杂区域划分、Dashboard
内容驱动项目沿主轴流动、自动伸缩用轨道(track)显式定义行列
对齐能力主轴 / 交叉轴两方向对齐更细粒度,可跨单元格控制
适合组件内部 / 单方向排布页面级 / 区域级整体骨架

选择建议

  • 只有一个方向的排布需求(横向导航、垂直列表、居中对齐)→ Flex

  • 同时要管行和列、需要定义网格区域(如 "左侧栏 + 右上 + 右下" 的 Dashboard)→ Grid

  • 混合页面(如 Grid 管整体骨架,骨架内部再用 Flex 排组件)→ 两者搭配,这也是现代 CSS 布局的主流组合。


八、总结

Flex 之所以能 "搞定绝大多数弹性布局",是因为它把过去需要 float、table、inline-block、绝对定位 + 各种 hack 才能实现的对齐、等分、伸缩、换行、排序,收敛成了容器上 6 个属性 + 项目上 6 个属性的清晰模型。

只要记住几条主线,整张知识网就串起来了:

  1. 定位display: flex 开启,记住 "容器 vs 项目"。

  2. 方向flex-direction 定主轴,flex-wrap 定换行。

  3. 对齐:主轴用 justify-content,交叉轴用 align-items(单行)与 align-content(多行)。

  4. 伸缩flex-grow / flex-shrink / flex-basis,日常直接用 flex: 1

  5. 特例order 改顺序,align-self 改个别对齐,margin: auto 做 "万能推挤"。

  6. 间距:一律 gap

  7. 兜底:内容撑破就补 min-width: 0

告别 float,拥抱 Flex,再配合 Grid 做页面级骨架 —— 现代 CSS 布局的绝大部分需求,这套组合就足够覆盖了。遇到问题别急着写样式,先在脑子里画一条主轴、一条交叉轴,答案往往就自己浮现出来了。