花了几个小时整合的”A Complete Guide to Flexbox”最新版本,介绍了flexbox的所有属性,外带几个实用的例子。

传统布局、Flexbox

布局的传统解决方案,基于盒状模型,依赖 display、position、float 三大属性。它对于那些特殊布局非常不方便,比如,垂直居中就不容易实现。

Flex布局,可以简便、完整、响应式地实现各种页面布局。目前,它已经得到了所有浏览器的支持,这意味着,现在就能很安全地使用这项功能。

Flex是Flexible Box的缩写,意为”弹性布局”,用来为盒状模型提供最大的灵活性。任何一个容器都可以指定为Flex布局。

背景

Flexbox提供了一种有效的方式来对容器内的元素布局、对齐、分配空间。能在不知道子元素大小或动态变化情况下分配好各个子元素间的间隙。(正如其名flex

Flex布局的主要思想是给父元素动态调整子元素的高度宽度的能力,使各元素适应可用布局空间。(能够适应不同的设备和不同大小的屏幕),一个flex容器可以放大子元素来填充可用空间,也可以缩小子元素来防止溢出。

最重要的是,与常规布局相比(垂直的块布局和水平的内联布局)Flexbox布局是方向未知的。传统布局对大型复杂应用的灵活性不是很好(特别是在改变方向,大小,伸展,收缩方面)

注:Flexbox布局适合应用组件或小规模的布局。Grid布局适合大规模的布局。

基本概念

Flex布局是一个完整的模块而不是一个单独的属性,它包括了完整的一套属性。其中有的属性是设置在容器(container,也可以叫做父元素,称为flex container)上,有的则是设置在容器的项目上(item,也可以叫做子元素,称为flex items)上。

如果我们可以说传统布局是建立在块状元素垂直流和行内元素水平流上的,那么flex布局就是建立在”flex-flow方向”上的,通过下图解释flex布局的主要思想。

在flex布局中,子元素要么按照主轴也就是main axis(从main-start到main-end)排布,要么按照交叉轴,也就是cross axis(从cross-start到cross-end)排布。

  • main axis: Flex 父元素的主轴是指子元素布局的主要方向轴,注意主轴不一定是水平的,它由属性flex-direction来确定主轴是水平还是垂直的。
  • main-start|main-end: 分别表示主轴的开始和结束,子元素在父元素中会沿着主轴从main-start到main-end排布。
  • main size: 子元素在主轴方向上的大小。包括长度和宽度。
  • cross axis: 交叉轴,与主轴垂直。
  • cross-start|cross-end: 分别表示交叉轴的开始和结束。子元素在交叉轴的排布从cross-start开始到cross-end。
  • cross size: 子元素在交叉轴方向上的大小。包括长度和宽度。

属性介绍

.container 是设置在容器上的,.item 是设置在子元素上的。

display 设置布局容器

.container {
  display: flex; /* or inline-flex */
}

用来定义父元素是一个flex布局容器。如果设置为flex则父元素为块状元素,设置为inline-flex父元素呈现为行内元素。它为它所有的直接子元素启用了一个伸缩上下文。

flex-direction 主轴排列方向

.container {
  flex-direction: row | row-reverse | column | column-reverse;
}

flex-direction定义flex布局的主轴方向。flex布局是单方向布局,子元素主要沿着水平行或者垂直列布局。

  • row(默认):在LTR排版方式下从左到右; RTL从右到左
  • row-reverse:LTR 从右到左; RTL 从左到右
  • column:从上到下
  • column-reverse:从下到上

flex-warp 是否换行

.container {
  flex-wrap: nowrap | wrap | wrap-reverse;
}

默认情况下,flex布局中父元素会把子元素尽可能地排在同一行,通过设置flex-wrap来决定是否允许子元素这行排列。

  • nowrap(默认值): 不折行,所有的子元素会排在一行。
  • wrap: 折行,子元素会从上到下根据需求折成多行。
  • wrap-reverse: 折行,子元素会从下到上根据需求折成多行。

flex-flow 缩写形式

.container {
  flex-flow: row nowrap; /* column wrap */
}

flex-flow是flex-direction和flex-wrap属性的缩写形式。默认值是row nowrap

justify-content 元素对齐、主轴

.container {
  justify-content: flex-start | flex-end | center | space-between | space-around | space-evenly | start | end | left | right ... + safe | unsafe;
}

justify-content属性定义了子元素沿主轴方向的对齐方式,当子元素大小最大时(如长度最大化不可再增加),分配主轴上的剩余空间(如宽度)。也可以当子元素超出主轴的时候用来控制子元素的对齐方式。

  • flex-start (默认值):项目位于容器的开头。
  • flex-end:项目位于容器的结尾。
  • center:项目沿着线居中
  • space-between:物品均匀分布在线上; 第一项是在起始行,最后一项是在结束行
  • space-around:项目均匀分布在线条周围,空间相等。请注意,视觉上空间不相等,因为所有项目在两侧都有相等的空间。第一个项目将在容器边缘上有一个空间单位,但在下一个项目之间有两个单位的空间,因为下一个项目有自己适用的间距。
  • space-evenly:项目是分布的,以便任何两个项目之间的间距(和边缘的空间)相等。

align-items 元素对齐、交叉轴

.container {
  align-items: stretch | flex-start | flex-end | center | baseline | first baseline | last baseline | start | end | self-start | self-end + ... safe | unsafe;
}

align-items定义了所在行的子元素在交叉轴方向的对齐方向。可以将其视为justify-content竖轴的版本(垂直于主轴)。

  • stretch(默认值): 拉伸子元素使之填充整个父元素。(遵守min-width,max-width

  • flex-start / start / self-start: 按照交叉轴的起点对齐。

  • flex-end / end / self-end: 按照交叉轴的终点对齐。

  • center: 沿交叉轴方向居中。

  • baseline: 按照项目的第一行文字的基线对齐。

align-content 各行对齐、交叉轴

.container {
  align-content: flex-start | flex-end | center | space-between | space-around | space-evenly | stretch | start | end | baseline | first baseline | last baseline + ... safe | unsafe;
}

align-content是当父元素所包含的行在交叉轴方向有空余部分时如何分配空间。与justify-content在主轴上如何对齐子元素很相似。

  • stretch (默认值):线条拉伸以占用剩余空间

  • flex-start:元素位于容器的开头

  • flex-end:元素位于容器的末尾

  • center:元素位于容器的中心

  • space-between:线条均匀分布; 第一行是容器的开头,而最后一行是在容器的最后

  • space-around:线条均匀分布,每条线周围的空间相等

注意当只有一行或flex-wrap为默认值的时候,该属性并不起作用。

gap 子元素行列间距

.container {
  display: flex;
  ...
  gap: 10px;
  gap: 10px 20px; /* row-gap column gap */
  row-gap: 10px;
  column-gap: 20px;
}

gap 属性明确控制子元素之间的空间。(边缘与子元素之间没效果)

order 子元素排列顺序

.item {
  order: 5; /* default is 0 */
}

默认情况下,子元素按照代码书写的先后顺序布局,但order属性可以更改子元素出现的顺序。

注:order值一样的子元素会按照默认按照代码书写的先后顺序布局。

flex-grow 子元素扩展比例

.item {
  flex-grow: 4; /* default 0 */
}

flex-grow规定在空间允许的情况下,子元素如何按照比例分配可用剩余空间。如果所有的子元素的属性都设定为1,则父元素中的剩余空间会等分给所有子元素。如果其中某个子元素的flex-grow设定为2,则在分配剩余空间时该子元素将获得其他元素二倍的空间(至少会尽力获得)。

注:flex-grow不接受负值。

flex-shrink 子元素缩小比例

.item {
  flex-shrink: 3; /* default 1 */
}

与flex-grow属性类似,flex-shrink定义了空间不足时项目的缩小比例。

注:flex-shrink不接受负值。

flex-basis 子元素理想大小

.item {
  flex-basis:  | auto; /* default auto */
}

flex-basis表示在flex items被放入flex容器之前的大小,也就是items的理想或者假设大小,但是并不是其真实大小,其真实大小取决于flex容器的宽度,如min-width,max-width等。

flex-basis:auto指子元素基本尺寸根据其自身的尺寸决定。而这个自身尺寸与下面这几个方面有关

  • box-sizing 盒模型(flex-basis的尺寸是作用在content-box上的)
  • width/min-width/max-width等
  • content内容(min-content)
  • flex-grow产生的额外空间

flex-basis设置特定值时,同时设定width,最小内容宽度较大的时候,会按照了最小内容宽度显示,而不是被width限制死尺寸。flex-basis优先级是比width高

  • width:100px + flex-basis:auto = 元素自身100px
  • content + flex-basis:100px = max(content, flex-basis) = 大于等于100px
  • content + width:100px + flex-basis:100px = content + flex-basis:100px = max(content, flex-basis) = 大于等于100px

width只是flex-basis为auto时候间接生效,其余时候使用优先级更高的flex-basis属性值;

/* 根据flex子项的内容自动调整大小 */
flex-basis: content; 尺寸根据内容决定

/* 内部尺寸关键字 */
flex-basis: fill;
flex-basis: max-content; 最大内容宽度。
flex-basis: min-content; 最小内容宽度。
flex-basis: fit-content;

注:由于浏览器兼容性的问题,flex-basis数值属性值和width数值属性值不要同时使用。

flex 缩写形式

.item {
  flex: none | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]
}

flex是flex-grow、flex-shrink、flex-basis三个属性的缩写。其中第二个和第三个参数(flex-grow,flex-basis)是可选的。默认值为0 1 auto。

注:推荐使用缩写形式而不是单独地设置每一个属性,缩写形式中会智能地计算出相关值。

align-self 单个元素对齐、交叉轴

.item {
  align-self: auto | flex-start | flex-end | center | baseline | stretch;
}

通过设置某个子元素的align-self属性,可以覆盖align-items所设置的对齐方式。

注:float、clear、vertical-align 对flex子元素无影响。

例子

水平垂直居中

<div class="parent">
<span class="child">水平垂直居中</span>
</div>
.parent {
  display: flex;
  height: 300px;
  border: 1px solid;
}

.child {
  width: 100px;
  height: 100px;
  line-height: 100px;
  margin: auto;
  border: 1px solid;
}

给子元素设置 marginauto,可以吸收额外的空间。使元素在两个轴上完美居中。

响应式初体验

考虑有6个子元素,有固定的大小,希望能够在改变浏览器宽度的时候仍然可以在水平轴上完美地显示。

<ul class="flex-container">
  <li class="flex-item">1</li>
  <li class="flex-item">2</li>
  <li class="flex-item">3</li>
  <li class="flex-item">4</li>
  <li class="flex-item">5</li>
  <li class="flex-item">6</li>
</ul>
.flex-container {
  /* 设置flex容器 */
  display: flex;
  
  /* Then we define the flow direction 
     and if we allow the items to wrap 
   * Remember this is the same as:
   * flex-direction: row;
   * flex-wrap: wrap;
   */
  flex-flow: row wrap;
  
  /* Then we define how is distributed the remaining space */
  justify-content: space-around;
  
  padding: 0;
  margin: 0;
  list-style: none;
}

.flex-item {
  background: tomato;
  padding: 5px;
  width: 200px;
  height: 150px;
  margin-top: 10px;
  line-height: 150px;
  color: white;
  font-weight: bold;
  font-size: 3em;
  text-align: center;
}

https://codepen.io/css-tricks/embed/EKEYob

响应式导航栏

一个向右对齐的导航栏在网页的最上端,我们希望它在中屏上显示时为居中,在小屏上以单列显示。

<ul class="navigation">
  <li><a href="#">Home</a></li>
  <li><a href="#">About</a></li>
  <li><a href="#">Products</a></li>
  <li><a href="#">Contact</a></li>
</ul>
.navigation {
  display: flex;
  flex-flow: row wrap;
  justify-content: flex-end;
  
  list-style: none;
  margin: 0; 
  background: deepskyblue;
}

.navigation a {
  text-decoration: none;
  display: block; /* 变成块级元素撑起整个navigation */
  padding: 1em;
  color: white;
}

.navigation a:hover {
  background: #1565C0;
}

/* 往中屏切换 */
@media all and (max-width: 800px) {
  .navigation {
    justify-content: space-around;
  }
}

/* 往小屏切换 */
@media all and (max-width: 600px) {
  .navigation {
    flex-flow: column wrap;
    padding: 0;
  }
  .navigation a { 
    text-align: center; 
    padding: 10px;
    border-top: 1px solid rgba(255, 255, 255,0.3); 
    border-bottom: 1px solid rgba(0, 0, 0, 0.1); 
  }
  .navigation li:last-of-type a {
    border-bottom: none;
  }
}

https://codepen.io/css-tricks/embed/YqaKYR

网页结构布局

<div class="wrapper">
  <header class="header">Header</header>
  <article class="main">
    <p>Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.</p>  
  </article>
  <aside class="aside aside-1">Aside 1</aside>
  <aside class="aside aside-2">Aside 2</aside>
  <footer class="footer">Footer</footer>
</div>
.wrapper {
  display: flex;  
  flex-flow: row wrap;
  font-weight: bold;
  text-align: center; 
}

.wrapper > * {
  padding: 10px;
  flex: 1 100%;
}

.header {
  background: tomato;
}

.footer {
  background: lightgreen;
}

.main {
  text-align: left;
  background: deepskyblue;
}

.aside-1 {
  background: gold;
}

.aside-2 {
  background: hotpink;
}

@media all and (min-width: 600px) {
  .aside { flex: 1 0 0; }
}

@media all and (min-width: 800px) {
  .main    { flex: 3 0px; }
  .aside-1 { order: 1; } 
  .main    { order: 2; }
  .aside-2 { order: 3; }
  .footer  { order: 4; }
}

body {
  padding: 2em; 
}

https://codepen.io/anon/embed/vWEMWw

更美观的标题

使用绝对定位的方式将文本放置在右边,因为不在文档流中,无法智能地决定何时换行。

使用float,在换行时不会有很好的左对齐效果。

使用表格,不会进行换行。

<h3 class="title abs-title">
  <span class="title-main">Main Title Here</span>
  <span class="title-note">This is absolutely positioned.</span>
</h3>

<h3 class="title float-title">
  <span class="title-main">Main Title Here</span>
  <span class="title-note">This subtitle is floated.</span>
</h3>

<h3 class="title table-title">
  <span class="title-main">Main Title Here</span>
  <span class="title-note">I am a table cell.</span>
</h3>

<h3 class="title flex-title">
  <span class="title-main">Main Title Here</span>
  <span class="title-note">This is a good look, right here.</span>
</h3>
body {
  padding: 100px;
  font-size: 21px;
}

.title {
  border-bottom: 1px solid #ccc;
  max-width: 500px;
  margin: 40px auto;
}
.title-note {
  font-size: 60%;
  color: #999;
}

.abs-title {
  position: relative;
  .title-note {
    position: absolute;
    bottom: 2px;
    right: 0;
  }
}

.float-title {
  .title-note {
    float: right;
    position: relative;
    top: 12px;
  }
}

.table-title {
  display: table;
  width: 100%;
  > span {
    display: table-cell;
    white-space: nowrap;
  }
  .title-main {
    width: 99%;
  }
}

.flex-title {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  > span {
    white-space: nowrap;  /* 强制在同一行内显示所有文本 */
  }
  .title-main {
    flex-grow: 1;  /* 让标题利用多余空间进行扩展,从而使副标题紧靠右边 */
  }
}

https://codepen.io/chriscoyier/embed/doVXLV

使用海报

下载:https://xiaonenglife.oss-cn-hangzhou.aliyuncs.com/static/pic/2022/09/20220916125123_css-flexbox-poster.png

参考资料

Flex 布局语法教程 | 菜鸟教程

A Complete Guide to Flexbox | CSS-Tricks – CSS-Tricks

flex-basis_奔跑吧、GZB的博客