今天在网上看到一张图,是关于 CSS 常见的布局方式汇总的一张图,如下

最近刚好也在复习相关内容,所以就借着这个机会从新整理一下 CSS 当中比较常见的一些布局方式,就当是复习复习布局相关知识点,下面我们就一个一个来介绍,示例集合可见 demos 当中的 CSS 板块
文档流布局
示例可见 文档流布局,也是 CSS 当中最基本的布局方式,就是按照文档的顺序一个一个显示出来,块元素独占一行,行内元素共享一行,如下

浮动布局
示例可见 浮动布局,也是比较常见的布局方式,不过在 flex 流行以后,这种方式已经使用的比较少了,原理是使用 float 属性,使元素脱离文档流,浮动起来,不过需要注意在使用该属性时引起的其他问题(譬如高度崩塌)

定位布局
示例可见 定位布局,与浮动布局类似,也是使元素脱离文档流,浮动起来,不过使用的是 position 属性,结果与浮动布局是一致的
至于两者有何区别,简单来说各有各的优缺点,很多时候我们希望控制的布局流问题不需要 float 和 position 实现,而应该通过显示模型定义来解决,比如现在的 column layout、flex box 等等,也就是下面将要说到的,都是在这个角度解决我们遇到的问题,它们应该才是正解
圣杯布局和双飞翼布局
两者的本质其实是差不多的,就是两边顶宽,中间自适应的三栏布局,中间栏要在放在文档流前面以优先渲染,详细的可以参考 CSS布局中圣杯布局与双飞翼布局的实现思路差异在哪里?,这里只截取部分
简单来说就是,双飞翼布局比圣杯布局多创建了一个 div,但是不需要使用相对布局了,圣杯布局和双飞翼布局解决问题的方案在前一半是相同的,也就是三栏全部 float 浮动,但左右两栏加上负 margin 让其跟中间栏 div 并排,以形成三栏布局,不同之处在于解决中间栏 div 内容不被遮挡问题的思路不一样
- 圣杯布局,为了中间
div内容不被遮挡,将中间div设置了左右padding-left和padding-right后,将左右两个div用相对布局position: relative并分别配合right和left属性,以便左右两栏div移动后不遮挡中间div - 双飞翼布局,为了中间
div内容不被遮挡,直接在中间div内部创建子div用于放置内容,在该子div里用margin-left和margin-right为左右两栏div留出位置
对比下来可以发现,双飞翼布局多了一个 div,少用大致四个 CSS 属性,至于具体使用哪种方案可以根据实际情况来选择
圣杯布局
实现以后如下所示,示例可见 圣杯布局

页面布局如下,很简单的三个 div 被外层父元素包裹,没用多余的额外元素
1 | <div class="wrapper"> |
核心 CSS 代码如下
1 | .wrapper { |
双飞翼布局
如果你手动实现一遍以后就会发现,圣杯布局是有弊端的,当你将浏览器宽度缩短到一定程度的时候,会使得中间子元素的宽度比左右子元素宽度小的时候,这时候布局就会出现问题,而双飞翼布局的出现就是为了解决此类问题,实现的结果与圣杯布局是一致的,示例可见 双飞翼布局
页面布局如下,与圣杯布局相比有些许不同,可以发现在 Content 的外部多了一层 div,而这个 div 就是用来解决上述问题的
1 | <div class="content-wrapper"> |
核心 CSS 代码如下
1 | .content-wrapper { |
flex 布局
关于 flex 部分内容主要截取自 flex 布局,感觉讲的比较好理解,图文相对应,不过其中有一些调整,并且添加了一些内容,主要是方便自己比较好理解,如果想了解更多的话可以参考原文
flex 是一种新型的布局方式,使用该布局方式可以实现几乎所有你想要的效果,但是要注意其浏览器的兼容性,flex 只支持 IE 10 以上,flex 的使用方法很简单,只需要将元素的 display 属性设置为 flex 就可以,也可以设置行内的 flex(inline-flex)
有一个特别需要需要的地方,当设置元素为
flex布局之后,子元素的float、clear、vertical-align的属性将会失效
在 flex 中,最核心的概念就是容器和轴,所有的属性都是围绕容器和轴设置的,其中容器分为父容器和子容器,轴分为主轴和交叉轴
主轴默认为水平方向,方向向右,交叉轴为主轴顺时针旋转
90°
在使用 flex 的元素中,默认存在两根轴
- 水平的主轴(
main axis)- 主轴开始的位置称为
main start - 结束的位置称为
main end
- 主轴开始的位置称为
- 垂直的交叉轴(
cross axis)- 交叉轴开始的位置称为
cross start - 结束的位置称为
cross end
- 交叉轴开始的位置称为
在使用 flex 的子元素中,占据的主轴空间叫做 main size,占据的交叉轴空间叫做 cross size,如下图所示

之前整理过一篇关于子容器的 flex 取值相关问题,可见 关于子容器属性 flex 取值问题,不过关于容器和元素的属性也只是一笔带过,所以就在之前的基础上扩充一下吧
父容器
首先,实现 flex 布局需要先指定一个容器,任何一个容器都可以被指定为 flex 布局,这样容器内部的元素就可以使用 flex 来进行布局
父容器可以统一设置子容器的排列方式,子容器也可以单独设置自身的排列方式,如果两者同时设置,以子容器的设置为准
有下面六种属性可以设置在父容器上,它们分别是
flex-direction,主轴的方向flex-wrap,超出父容器子容器的排列样式flex-flow,flex-direction属性和flex-wrap属性的简写形式justify-content,子容器在主轴的排列方向align-items,子容器在交叉轴的排列方向align-content,多根轴线的对齐方式
下面我们就来一个一个深入了解一下
flex-direction
决定主轴的方向(即项目的排列方向),但是主轴的方向不一定是水平的,这个属性就是设置主轴的方向,默认是水平方向,从左至右,如果主轴方向设置完毕,那么交叉轴就不需要设置,交叉轴永远是主轴顺时针旋转 90°
1 | .div { |

flex-wrap
flex-wrap 属性决定子容器是否换行排列,不但可以顺序换行而且支持逆序换行
1 | .ele { |

justify-content
主要用于定义如何沿着主轴方向排列子容器
1 | .ele{ |

flex-flow
flow 即流向,也就是子容器沿着哪个方向流动,流动到终点是否允许换行,比如 flex-flow: row wrap,flex-flow 是一个复合属性,相当于 flex-direction 属性和 flex-wrap 属性的简写形式,默认值为 row nowrap
row、column等,可单独设置主轴方向wrap、nowrap等,可单独设置换行方式row nowrap、column wrap等,也可两者同时设置
align-items
align-items 属性定义子容器在交叉轴上如何对齐,具体的对齐方式与交叉轴的方向有关,假设交叉轴从上到下
1 | .ele{ |

align-content
align-content 属性定义了多根轴线的对齐方式,如果项目只有一根轴线,该属性不起作用,简单来说就是当子容器多行排列时,设置行与行之间的对齐方式
1 | .ele{ |

子容器
子容器也有六个属性
order,子容器的排列顺序flex-grow,子容器剩余空间的拉伸比例flex-shrink,子容器超出空间的压缩比例flex-basis,子容器在不伸缩情况下的原始尺寸flex,flex-grow,flex-shrink和flex-basis的简写align-self,允许子容器与其他项目采用不一样的对齐方式
order
order 属性定义项目的排列顺序,可以为负值,数值越小,排列越靠前,默认为 0
1 | .ele{ |

flex-grow
flex-grow 属性定义子容器的伸缩比例,按照该比例给子容器分配空间,默认值为 0
1 | .ele{ |

flex-shrink
flex-shrink 属性定义了子容器弹性收缩的比例,如图,超出的部分按 1: 2 的比例从给子容器中减去
此属性要生效,父容器的
flex-wrap属性要设置为nowrap,默认值为0
1 | .ele{ |

flex-basis
flex-basis 属性定义了子容器在不伸缩情况下的原始尺寸,主轴为横向时代表宽度,主轴为纵向时代表高度,默认值为 0
1 | .ele{ |

flex
子容器是有弹性的,它们会自动填充剩余空间,子容器的伸缩比例由 flex 属性确定,flex 的值可以是无单位数字(如:1,2,3),也可以是有单位数字(如:15px,30px,60px),还可以是 none 关键字,子容器会按照 flex 定义的尺寸比例自动伸缩,如果取值为 none 则不伸缩,虽然 flex 是多个属性的缩写,允许 1 - 3 个值连用,但通常用 1 个值就可以满足需求,它的全部写法可参考下图

具体取值规则可以参考之前整理的一篇文章 关于子容器属性 flex 取值问题
align-self
子容器的 align-self 属性允许单个项目有与其他项目不一样的对齐方式,它会覆盖父容器 align-items 属性,如果两者同时设置则以子容器的 align-self 属性为准,默认值为 auto,表示继承父元素的 align-items 属性,如果没有父元素,则等同于 stretch
1 | .ele{ |

下面是一张各属性的汇总图

grid 布局
主要参考的 Learn CSS Grid in 5 minutes,按照惯例,在使用之前我们先来看一下 CSS Grid 的兼容性,如下

可以发现,主流浏览器(Safari,Chrome,Firefox,Edge)的支持还是不错的,那么我们就来看看 CSS Grid 到底是个什么东西,全部示例可见 网格布局 和 网格版圣杯布局
基本概念
我们先从一个简单的示例开始,然后在深入了解其中的概念,CSS Grid 布局由两个核心组成部分是 wrapper(父元素)和 items(子元素),wrapper 是实际的 grid(网格),items 是网格内的内容,下面是一个 wrapper 元素,内部包含六个 items
1 | <div class="wrapper"> |
要把 wrapper 元素变成一个网格,只要简单地把其 display 属性设置为 grid 即可
1 | .wrapper { |
在这种情况下,我们并没有做其他的操作,它只会简单地将六个 div 堆叠在一起,如下图所示

为了使其成为二维的网格容器,我们需要定义列和行,让我们创建 3 列和 2 行,我们将使用 grid-template-row 和 grid-template-column 属性
1 | .wrapper { |
这些值决定了我们希望我们的列有多宽(100px),以及我们希望行数是多高(50px),结果如下

也可以来稍微的调整一下,比如应用以下 CSS 样式
1 | .wrapper { |
就变成了如下

此时,我们已经了解了 CSS Grid 布局的基本过程,下面我们就来了解一下相应的术语
网格容器(Grid Container)
应用 display: grid 的元素,就是所有网格项(grid item)的直接父级元素,在上面的例子中 wrapper 就是网格容器(Grid Container)
1 | <div class="wrapper"> |
网格项(Grid Item)
网格容器(Grid Container)的子元素(例如直接子元素),比如下面这里 item 元素就是网格项(Grid Item),但是 sub-item 不是
1 | <div class="wrapper"> |
网格线(Grid Line)
网格线组成了网格,他是网格的水平和垂直的分界线,一个网格线存在行或列的两侧,我们可以引用它的数目或者定义的网格线名称

网格轨道(Grid Track)
网格轨道是就是相邻两条网格线之间的空间,就好比表格中行或列,所在在网格中其分为 grid column 和 grid row,每个网格轨道可以设置一个大小,用来控制宽度或高度

网格单元格(Grid Cell)
网格单元格是指四条网格线之间的空间,所以它是最小的单位,就像表格中的单元格

网格区域(Grid Area)
网格区域是由任意四条网格线组成的空间,所以他可能包含一个或多个单元格,相当于表格中的合并单元格之后的区域

以上就是网格布局中的一些基本概念,下面我们来看具体的操作方法
使用 Grid 布局
我们还是在最开始部分的示例之上来进行扩展,首先还是先创建一个 3×3 的网格
需要注意的是,同
flex布局一样,当元素设置了网格布局之后,元素的column、float、clear、vertical-align属性都是无效的
1 | <div class="wrapper"> |
应用的 CSS 代码如下
1 | .wrapper { |
将得到以下布局

细心对比可以发现,我们只在页面上看到 3×2 的网格,而我们定义的则是 3×3 的网格,这是因为我们只有六个子元素来填满这个网格,如果我们再加三个子元素,那么最后一行也会被填满

如果还存在更多的子元素,则是被忽略(即默认是不可见的)
如果要定位和调整子元素大小,我们将使用 grid-column 和 grid-row 属性来设置
1 | .item1 { |
我们在这里要做的是,我们希望 item1 占据从第一条网格线开始,到第四条网格线结束,换句话说,它将独立占据整行, 以下是在屏幕上显示的内容

可以发现,当我们把第一个子元素设置为占据整个第一行时,它会把剩下的子元素都推到了下一行,至于为什么会有四条网格线,可以参考下面这个图

当然,也可以使用简写方式来实现
1 | .item1 { |
下面我们就可以将所有的子元素进行自定义排列
1 | .item1 { |
也可以采用简写的方式
1 | .item1 { |
最终结果如下

我们也可以像 flex 一样设置每一列的宽度
1 | .wrapper { |
可以发现,在上面的示例当中我们使用了一个新的单位 fr
fr单位是一个自适应单位,fr单位被用于在一系列长度值中分配剩余空间,如果多个已指定了多个部分,则剩下的空间根据各自的数字按比例分配
fr是基于网格容器可用空间来计算的(flex也是一样),所以我们可以和其他单位混合使用(如果需要的话)
结果如下

也可以利用这个特性来进行单元格的合并

行或列最小和最大尺寸
minmax() 函数来创建行或列的最小或最大尺寸,第一个参数定义网格轨道的最小值,第二个参数定义网格轨道的最大值,可以接受任何长度值,也接受 auto 值,auto 值允许网格轨道基于内容的尺寸拉伸或挤压,我们来试试将第一行的高度设置为 minmax(100px, 200px),第二行的高度设置为 minmax(50px, 200px),容器总高度设置为 300px,如下
1 | .wrapper { |
具体逻辑为,先判断『总高度』和『第一列高度的最大值和第二列高度的最大值之和』的两者之间的大小
- 如果大于,那么第一列和第二列的高度都为设置的最大值
- 如果是小于,那么第一列和第二列的高度都为设置的最小值
在上面的情况当中是属于小于的情况,所以会先使用总高度减去两列的最小高度得出一个值,在根据这个值来计算最终的高度
- 总高度为,第一列最小高度 - 第二列最小高度 =
150px(300px - 100px - 50px = 150px) - 第一列高度为,(第一列最小高度
100px)+ 150px/2 = 175px - 第二列高度为,(第一列最小高度
50px)+ 150px/2 = 125px
如下图所示

重复行或者列
repeat() 属性可以创建重复的网格轨道,这个适用于创建相等尺寸的网格项目和多个网格项目,repeat() 也接受两个参数,第一个参数定义网格轨道应该重复的次数,第二个参数定义每个轨道的尺寸,简单来说就是简化同样的赋值操作,结果如下图

间距
主要使用 grid-column-gap 和 grid-row-gap 两个属性
grid-column-gap,创建列与列之间的距离grid-row-gap,行与行之间的距离grid-gap,简写方式,第一个参数为行间距,第二个参数为列间距
1 | .wrapper { |

网格区域(grid-template-areas)
所谓网格区域(网格区域),简单来说就是一个逻辑空间,主要用来放置一个或多个网格单元格(Grid Cell),它由四条网格线(Grid line)构成,网格区域每边一条,四边相交组织的网格轨道(Grid Track)
简单点理解,网格区域是有四条网格线交织组成的网格空间,这个空间中可能是一个网格单元格,也可能是多个网格单元格,在 CSS Grid Layout 中定义网格区域有两种方式,一种是通过网格线来定义,另一种是通过 grid-template-areas 来定义
网格线定义网格区域
简单来说有下面三个步骤
- 使用网格线定义网格区域的方法非常的简单,首先依赖于
grid-template-columns和grid-template-rows显式定义网格线,甚至是由浏览器隐式创建网格线 - 然后通过
grid-area属性通过取网格线,组成网格线交织区域,那么这个区域就是所讲的网格区域 - 在使用
grid-area属性调用网格线,其遵循的取值规则如下
1 | grid-area: row-start/column-start/row-end/column-end |
使用 grid-template-areas 定义网格区域
除了使用网格线的交组来定义网格区域之外,在 CSS Grid Layout 中还可以通过 grid-template-areas 属性来定义网格区域的名称,然后需要放在对应网格区域的元素,可以通过 grid-area 属性来指定,而且重复区域可以使用同一个名称来实现跨区域,另外对于空的轨道区域,可以使用点号 . 来代表
具体的使用细节我们就通过下面这个实例来充分的了解一下所谓的网格区域
实例
我们要实现的最终结果如下

接下来,我们就一步一步来进行实现
HTML 结构
首先我们需要一个容器,如下
1 | <div class="container"> |
设置基本的 CSS
在来设置一些基本的 CSS
1 | .container { |
经过前面的一些铺垫,这里应用的 CSS 应该很容易理解
- 使用
grid-template-columns属性创建一个12列的网格,每个列都是一个单位宽度(总宽度的1/12) - 使用
grid-template-rows属性创建3行,第一行高度是50px,第二行高度是350px和第三行高度是50px - 最后,使用
grid-gap属性在网格中的网格项之间添加一个间隙
添加 grid-template-areas
下面就是重点部分,来添加我们的网格区域,简单来说也就是定义 grid-template-areas 属性
1 | .container { |
grid-template-areas 属性背后的逻辑是你在代码中创建的网格可视化表示,正如所见,它有 3 行 12 列,和我们在 grid-template-columns 和 grid-template-rows 中定义的正好呼应,每行代表一行,用网格术语来说是 网格轨道(Grid Track) ,每个字符(h,m,c,f)代表一个网格单元格,四个字母中的每一个现在都形成一个矩形 grid-area(当然你也可以使用自定义字符)
给网格项设定网格区域名称
现在我们需要将这些字符与网格中的网格项建立对应的连接,要做到这一点,我们将在网格项使用 grid-area 属性
1 | .header { |
最终的结果如下(添加了一些额外的 CSS 样式用于展示)

尝试其他布局
现在,我们可以探讨一下使用网格布局的精妙之处,因为我们可以很容易地对布局进行修改尝试,只需修改 grid-template-areas 属性的字符即可,举个例子,比如把 menu 移到右边
1 | .wrapper { |

可以使用点 . 来创建空白的网格单元格
1 | .wrapper { |

还可以添加响应式布局
1 | @media screen and (max-width: 640px) { |
最终结果如下
