做前端的,谁没被float布局折磨过?

我还记得刚学CSS的时候,布局全靠float。要做个两栏布局?左边float:left,右边float:right,然后父元素overflow:hidden清除浮动。要做个导航栏?li全部float:left,然后ul overflow:hidden。要做个卡片列表?每个卡片float:left,然后算宽度、算margin、算一行放几个,最后一行还经常对不齐。

float布局的问题,说多了都是泪:

  • 父元素高度塌陷,必须清除浮动(overflow:hidden或者clearfix)
  • 居中难,垂直居中更是难上加难
  • 等高列难,两栏内容不一样高就对不齐底部
  • 排列顺序不灵活,想调换顺序只能改HTML
  • 响应式麻烦,不同屏幕尺寸要算各种宽度
  • 一个元素float了,后面的元素都要处理,牵一发而动全身

2016年,Flexbox(弹性盒子布局)终于被主流浏览器全面支持了。Chrome、Firefox、Safari、Edge都支持,甚至IE11也支持(虽然有一些bug)。我在项目中开始尝试用Flexbox,用了之后就再也回不去了——原来布局可以这么简单、这么优雅。

今天就来聊聊Flexbox,从float的痛点到Flexbox的用法,从核心概念到实际案例,分享一下我这半年多使用Flexbox的真实体验。

一、float布局的那些痛

在聊Flexbox之前,先回忆一下float布局给我们带来的痛苦。如果你是前端老兵,这些场景一定很熟悉;如果你是新手,恭喜你,Flexbox可以让你避开这些坑。

1. 父元素高度塌陷

这是float最经典的问题。子元素float之后,脱离了文档流,父元素"撑不起来",高度变成0。

<div class="parent">
  <div class="child" style="float:left;">子元素1</div>
  <div class="child" style="float:left;">子元素2</div>
</div>

父元素的高度是0!因为子元素都float了,脱离了文档流,父元素"看不到"它们。

解决方案:

  • 父元素overflow:hidden(但是会隐藏溢出内容)
  • 父元素也float(但是会影响其他元素)
  • 用clearfix伪元素(最常用,但是要写一堆CSS)
.clearfix::after {
  content: '';
  display: block;
  clear: both;
}

每一个有float子元素的父元素,都要加clearfix。烦不烦?

2. 垂直居中难

CSS里,水平居中简单(margin:0 auto或者text-align:center),但是垂直居中?那是前端界的"世纪难题"。

float布局下,垂直居中的方法:

  • 知道高度:用绝对定位+margin-top负一半
  • 不知道高度:用display:table-cell(但是要改变HTML结构)
  • 用line-height(只适用于单行文本)
  • 用CSS3 transform(但是兼容性和性能有问题)

每一种方法都有局限性,都不优雅。一个简单的垂直居中,要写一堆hack,这合理吗?

3. 等高列难

两栏布局,左边内容少,右边内容多,结果左边短右边长,底部对不齐。要做等高列?

float布局下的方法:

  • 用背景图模拟(假等高,不是真等高)
  • 用padding-bottom+margin-bottom负(hack,而且有副作用)
  • 用JS算高度(性能差,而且JS加载前不对)
  • 用display:table(改变布局方式,不灵活)

每一种都是hack,没有一种是"正解"。

4. 排列顺序不灵活

float布局下,元素的排列顺序由HTML顺序决定。想在大屏幕上左A右B,小屏幕上B在上A在下?只能用JS调换DOM顺序,或者写两套HTML。

响应式布局下,这种需求很常见,但是float布局处理起来非常麻烦。

5. 响应式计算麻烦

做响应式卡片列表,一行放几个?每个卡片宽度多少?margin多少?要算半天,而且不同屏幕尺寸要算不同的值,媒体查询写一堆。

.card {
  float: left;
  width: 31%;
  margin-right: 3.5%;
}
.card:nth-child(3n) {
  margin-right: 0;
}
@media (max-width: 768px) {
  .card {
    width: 48%;
    margin-right: 4%;
  }
  .card:nth-child(3n) {
    margin-right: 4%;
  }
  .card:nth-child(2n) {
    margin-right: 0;
  }
}

算宽度、算margin、清除最后一列的margin,烦不胜烦。

6. 一个float,处处要处理

最烦的是,一个元素float了,后面的元素都要处理。父元素要清除浮动,后面的元素要clear,布局被打乱了要调整。牵一发而动全身,改一个地方可能影响好几个地方。

float本来是用来做"文字环绕图片"的,结果被我们拿来做整个页面的布局,这本身就是"滥用"。Flexbox的出现,就是为了让布局回归正途。

二、Flexbox的核心概念

Flexbox(Flexible Box,弹性盒子)是CSS3的一种布局模式,专门为布局设计。它的核心思想是:让容器有能力让其子项目能够改变其宽度、高度(甚至顺序),以最佳方式填充可用空间

Flexbox有两个核心概念:Flex容器(Flex Container)Flex项目(Flex Item)

1. Flex容器和Flex项目

<div class="container">
  <div class="item">项目1</div>
  <div class="item">项目2</div>
  <div class="item">项目3</div>
</div>
.container {
  display: flex; /* 或者 display: inline-flex */
}
  • .container就是Flex容器,设置display:flex后,它的直接子元素就变成了Flex项目
  • .item就是Flex项目,它们会按照Flexbox的规则排列

注意:只有直接子元素才是Flex项目,孙子元素不受影响。

2. 主轴和交叉轴

Flexbox有两个轴:主轴(Main Axis)交叉轴(Cross Axis)

  • 主轴:Flex项目排列的方向(默认是水平方向,从左到右)
  • 交叉轴:垂直于主轴的方向(默认是垂直方向,从上到下)

主轴的方向由flex-direction属性决定:

  • row(默认):主轴水平,从左到右
  • row-reverse:主轴水平,从右到左
  • column:主轴垂直,从上到下
  • column-reverse:主轴垂直,从下到上

理解主轴和交叉轴非常重要,因为很多Flexbox属性都是针对主轴或交叉轴的。

3. Flexbox的优势

Flexbox相比float布局的优势:

  • 不需要清除浮动:Flex容器自动包裹子元素,不会高度塌陷
  • 垂直居中简单:一行代码搞定垂直居中
  • 等高列天然支持:Flex项目默认等高
  • 排列顺序灵活:用order属性可以随意调整顺序,不用改HTML
  • 响应式简单:用flex-wrap和媒体查询,响应式布局非常简单
  • 对齐方式丰富:主轴、交叉轴都有多种对齐方式
  • 空间分配灵活:用flex-growflex-shrinkflex-basis可以灵活分配空间

三、Flexbox常用属性详解

Flexbox的属性分为两类:容器属性(设置在Flex容器上)和项目属性(设置在Flex项目上)。

1. 容器属性

flex-direction:主轴方向

.container {
  flex-direction: row;            /* 默认,水平从左到右 */
  flex-direction: row-reverse;    /* 水平从右到左 */
  flex-direction: column;         /* 垂直从上到下 */
  flex-direction: column-reverse; /* 垂直从下到上 */
}

flex-wrap:是否换行

.container {
  flex-wrap: nowrap;       /* 默认,不换行,项目会被压缩 */
  flex-wrap: wrap;         /* 换行,超出的项目换到下一行 */
  flex-wrap: wrap-reverse; /* 换行,但是从下往上排 */
}

这是做响应式卡片列表的关键!设置flex-wrap:wrap后,项目超出容器宽度会自动换行,不需要算宽度和margin。

flex-flow:flex-direction和flex-wrap的简写

.container {
  flex-flow: row wrap; /* 等同于 flex-direction:row; flex-wrap:wrap; */
}

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;  /* 所有间隔相等(包括两端) */
}

这是做水平居中、两端对齐导航栏的关键!

align-items:交叉轴对齐方式

.container {
  align-items: stretch;     /* 默认,项目拉伸填满交叉轴(等高列!) */
  align-items: flex-start;  /* 顶部对齐 */
  align-items: flex-end;    /* 底部对齐 */
  align-items: center;      /* 垂直居中! */
  align-items: baseline;    /* 基线对齐 */
}

看到了吗?align-items:center就是垂直居中!一行代码搞定,再也不用写hack了。align-items:stretch就是等高列,天然支持!

align-content:多行对齐方式(只有一行时无效)

.container {
  align-content: stretch;       /* 默认,每行拉伸填满 */
  align-content: flex-start;    /* 顶部对齐 */
  align-content: flex-end;      /* 底部对齐 */
  align-content: center;        /* 居中 */
  align-content: space-between; /* 两端对齐 */
  align-content: space-around;  /* 每行两侧间隔相等 */
}

2. 项目属性

order:项目排列顺序

.item {
  order: 0; /* 默认,数值越小越靠前,可以是负数 */
}
.item-1 { order: 2; }
.item-2 { order: 1; } /* 这个会排在前面 */
.item-3 { order: 3; }

响应式布局神器!大屏幕上A在左B在右,小屏幕上想让B在上A在下?给B设置order:-1就行了,不用改HTML,不用JS。

flex-grow:项目放大比例

.item {
  flex-grow: 0; /* 默认,不放大 */
}
.item-1 { flex-grow: 1; }
.item-2 { flex-grow: 2; } /* 这个会占更多空间 */

如果容器有剩余空间,项目会按flex-grow的比例分配剩余空间。所有项目flex-grow都是1的话,平均分配剩余空间。

flex-shrink:项目缩小比例

.item {
  flex-shrink: 1; /* 默认,空间不足时会缩小 */
}
.item-1 { flex-shrink: 0; } /* 这个不会缩小 */

如果容器空间不足,项目会按flex-shrink的比例缩小。设置flex-shrink:0的项目不会缩小,保持原始宽度。

flex-basis:项目初始大小

.item {
  flex-basis: auto; /* 默认,项目的原始大小 */
  flex-basis: 200px; /* 初始宽度200px */
  flex-basis: 50%; /* 初始宽度50% */
}

flex-basis设置项目在分配剩余空间之前的初始大小。

flex:flex-grow、flex-shrink、flex-basis的简写

.item {
  flex: 0 1 auto; /* 默认,等同于 flex-grow:0; flex-shrink:1; flex-basis:auto; */
  flex: 1;        /* 等同于 flex-grow:1; flex-shrink:1; flex-basis:0%; 平均分配空间 */
  flex: none;     /* 等同于 flex-grow:0; flex-shrink:0; flex-basis:auto; 不放大不缩小 */
  flex: auto;     /* 等同于 flex-grow:1; flex-shrink:1; flex-basis:auto; 自动分配 */
}

这是最常用的简写,flex:1表示项目平均分配容器空间,做自适应布局非常方便。

align-self:单个项目的交叉轴对齐方式(覆盖容器的align-items)

.item {
  align-self: auto;         /* 默认,继承容器的align-items */
  align-self: flex-start;   /* 顶部对齐 */
  align-self: flex-end;     /* 底部对齐 */
  align-self: center;       /* 居中 */
  align-self: stretch;      /* 拉伸 */
  align-self: baseline;     /* 基线对齐 */
}

某个项目想单独对齐?用align-self覆盖容器的设置。

四、实际布局案例

光说不练假把式,下面用几个实际案例看看Flexbox有多好用。

1. 水平垂直居中

这是前端最常见的需求之一,Flexbox一行代码搞定:

<div class="container">
  <div class="box">我是居中的盒子</div>
</div>
.container {
  display: flex;
  justify-content: center; /* 主轴居中(水平) */
  align-items: center;     /* 交叉轴居中(垂直) */
  height: 300px;           /* 容器要有高度才能看出垂直居中 */
}

就这么简单!不需要绝对定位,不需要transform,不需要table-cell,两行CSS搞定水平垂直居中。

2. 导航栏

经典的导航栏布局:Logo在左,导航链接在右,链接之间有间隔。

<nav class="navbar">
  <div class="logo">Logo</div>
  <ul class="nav-links">
    <li><a href="#">首页</a></li>
    <li><a href="#">文章</a></li>
    <li><a href="#">关于</a></li>
  </ul>
</nav>
.navbar {
  display: flex;
  justify-content: space-between; /* Logo左,链接右 */
  align-items: center;            /* 垂直居中 */
  padding: 0 20px;
  height: 60px;
}
.nav-links {
  display: flex;        /* 链接也用Flexbox */
  list-style: none;
  margin: 0;
  padding: 0;
  gap: 20px;            /* 链接之间间隔20px(gap是Flexbox的好东西) */
}

不需要float,不需要clearfix,不需要算margin,导航栏就做好了。而且垂直居中天然对齐,不管Logo多高、链接多高,都能居中对齐。

3. 等高两栏布局

左边侧边栏,右边主内容,两栏等高(不管哪边内容多)。

<div class="layout">
  <aside class="sidebar">侧边栏</aside>
  <main class="content">主内容</main>
</div>
.layout {
  display: flex;
}
.sidebar {
  width: 250px;
  flex-shrink: 0; /* 侧边栏不缩小,固定250px */
}
.content {
  flex: 1; /* 主内容占满剩余空间 */
}

就这么简单!两栏天然等高(因为align-items默认是stretch),侧边栏固定宽度,主内容自适应。不需要背景图模拟等高,不需要JS算高度,不需要padding-bottom hack。

4. 响应式卡片列表

卡片列表,大屏幕一行4个,中屏幕一行3个,小屏幕一行2个,超小屏幕一行1个。

<div class="card-list">
  <div class="card">卡片1</div>
  <div class="card">卡片2</div>
  <div class="card">卡片3</div>
  <!-- ...更多卡片 -->
</div>
.card-list {
  display: flex;
  flex-wrap: wrap;          /* 允许换行 */
  gap: 20px;                 /* 卡片之间间隔20px */
}
.card {
  flex: 1 1 calc(25% - 15px); /* 一行4个,减去gap的影响 */
  min-width: 200px;            /* 最小宽度,防止太小 */
}
@media (max-width: 992px) {
  .card { flex: 1 1 calc(33.333% - 13px); } /* 一行3个 */
}
@media (max-width: 768px) {
  .card { flex: 1 1 calc(50% - 10px); } /* 一行2个 */
}
@media (max-width: 480px) {
  .card { flex: 1 1 100%; } /* 一行1个 */
}

flex-wrap:wrapgap,卡片自动换行,间隔均匀,不需要算margin、不需要nth-child清除最后一列的margin。响应式只需要改flex-basis的百分比,非常简洁。

5. 底部固定(Sticky Footer)

页面内容不够多时,页脚贴在底部;内容多时,页脚被内容推下去。

<div class="page">
  <header>头部</header>
  <main class="content">内容</main>
  <footer>页脚</footer>
</div>
.page {
  display: flex;
  flex-direction: column; /* 垂直排列 */
  min-height: 100vh;      /* 至少占满视口高度 */
}
.content {
  flex: 1; /* 内容区占满剩余空间,把页脚推到底部 */
}

经典的Sticky Footer布局,Flexbox几行代码搞定。不需要绝对定位,不需要calc,不需要给内容区加padding-bottom。

6. 输入框+按钮组合

输入框占满剩余空间,按钮固定宽度在右边。

<div class="search-box">
  <input type="text" placeholder="搜索...">
  <button>搜索</button>
</div>
.search-box {
  display: flex;
}
.search-box input {
  flex: 1; /* 输入框占满剩余空间 */
}
.search-box button {
  flex-shrink: 0; /* 按钮不缩小 */
}

输入框自适应宽度,按钮固定在右边,不需要float,不需要算宽度。

五、浏览器兼容性

2016年,Flexbox的兼容性已经非常好了:

  • Chrome 29+(2013年)
  • Firefox 28+(2014年)
  • Safari 9+(2015年,iOS 9+)
  • Edge 12+(2015年)
  • IE 11(部分支持,有一些bug)

IE10及以下不支持(IE10支持旧版语法,需要加前缀)。

如果需要兼容IE11,需要注意:

  • IE11不支持gap属性(用margin代替)
  • IE11的flex-basis有bug,有时候需要显式设置宽度
  • IE11的min-height和Flexbox配合有问题
  • 有些属性需要加-ms-前缀

2016年,大部分项目已经不需要兼容IE10及以下了,IE11的bug也有workaround。如果你的项目不需要兼容IE,那Flexbox可以放心大胆地用。

可以用Autoprefixer自动添加前缀,不需要手动写。

六、Flexbox的最佳实践和坑

用了半年多Flexbox,总结了一些最佳实践和需要注意的坑:

最佳实践:

  1. 优先用Flexbox做一维布局:Flexbox最适合做一维布局(一行或一列),二维布局(行和列都要控制)可以用CSS Grid(2016年还不太成熟)。
  2. 用gap做间隔gap属性比margin更简洁,不会有最后一列margin的问题。(IE11不支持,需要兼容的话用margin)
  3. 用flex简写flex:1比分别写flex-growflex-shrinkflex-basis更简洁。
  4. 容器和项目属性分清楚:哪些属性设置在容器上,哪些设置在项目上,不要搞混。
  5. 善用order做响应式:响应式布局需要调整顺序时,用order而不是改HTML或JS。
  6. 移动端优先:先写移动端的Flexbox布局,再用媒体查询适配大屏幕。

需要注意的坑:

  1. Flex项目默认不缩小到内容以下flex-shrink:1默认会缩小,但是不会缩小到内容以下。如果内容很长,项目可能不会按预期缩小。可以设置min-width:0overflow:hidden解决。
  2. Flex项目的margin不会合并:和普通块级元素不同,Flex项目的margin不会合并,这通常是好事,但有时候会和预期不同。
  3. absolute定位的子元素不是Flex项目:如果子元素设置了position:absolute,它就脱离了Flexbox布局,不是Flex项目了。
  4. Flex容器的column-count无效:Flex容器上设置column-count(多列布局)会无效。
  5. IE11的各种bug:如果需要兼容IE11,要多测试,很多属性在IE11上表现不同。
  6. 不要滥用Flexbox:Flexbox不是万能的,简单的布局(如单个元素居中)可以用更简单的方法,不需要都套Flexbox。

七、总结

用了Flexbox之后,我真的再也回不去float布局了。原来布局可以这么简单、这么优雅、这么符合直觉。

Flexbox解决了float布局的所有痛点:

  • 不需要清除浮动(父元素不会高度塌陷)
  • 垂直居中一行代码搞定
  • 等高列天然支持
  • 排列顺序灵活(order属性)
  • 响应式简单(flex-wrap + 媒体查询)
  • 对齐方式丰富(justify-content、align-items)
  • 空间分配灵活(flex-grow、flex-shrink、flex-basis)

Flexbox核心概念:

  • Flex容器和Flex项目
  • 主轴和交叉轴
  • 容器属性:flex-direction、flex-wrap、flex-flow、justify-content、align-items、align-content
  • 项目属性:order、flex-grow、flex-shrink、flex-basis、flex、align-self

实际案例:水平垂直居中、导航栏、等高两栏、响应式卡片、Sticky Footer、输入框+按钮组合,Flexbox都能简洁优雅地实现。

2016年,Flexbox的兼容性已经非常好,主流浏览器都支持,IE11有一些bug但是有workaround。大部分项目都可以放心使用Flexbox。

如果你还在用float布局,我强烈建议你试试Flexbox。一开始可能需要适应一下主轴、交叉轴这些概念,但是一旦上手,你会发现布局从来没有这么简单过。用了Flexbox之后,你也会和我一样,再也回不去float布局了。

最后,Flexbox不是终点,CSS Grid(二维布局)已经在来的路上了,未来的CSS布局会越来越强大。但是在2016年这个时间点,Flexbox就是最好用的布局工具,没有之一。

愿每一个前端开发者都能摆脱float的折磨,享受Flexbox带来的优雅布局。写CSS,本来就应该是一件愉快的事情。