CSS布局

现代网站都需要各种布局以保证页面展示效果,本文就说说布局

布局类型

视觉类型

不同布局看起来的效果不一样

主要有 3 种

  1. 两栏布局,很少单独使用了,一般用在给三栏布局的中间部分做两栏布局
  2. 三栏布局(多栏布局同三栏思路),如淘宝首页,两侧留空,中间保有内容
  3. 平均布局,如淘宝首页下方的 猜你喜欢,平均排列各种商品

技术类型

可以选用不同的方案实现布局

主要有 4 种

  1. 纯文档流布局
  2. float 浮动布局
  3. flex 弹性盒子布局
  4. grid 网格布局

本文以技术类型为轴,分别尝试实现 3 种视觉类型

本文中的两栏布局采用如下设计图

如图,画得不太准,认为是方形就好了,同时为了展示效果,在代码中会放大 10 倍

三栏布局采用 淘宝首页 作为示例

平均布局采用 bilibili 番剧区作为示例

纯文档流

纯文档流布局,利用了文档流本身的特点——表现为块级的元素逐行排列,表现为内联的元素同行自左向右排列

两栏布局

由图片可得,大致有如下结构的 HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>
<div class="main">
<div class="left">
<div class="large"></div>
<div class="small"></div>
<div class="small"></div>
</div>
<div class="right">
<div class="middle"></div>
<div class="middle"></div>
<div class="middle"></div>
</div>
</div>
</body>

使用如下 CSS,即可完成两栏布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
* {
margin: 0;
padding: 0;
box-sizing: border-box;
} /* reset */

.main {
font-size: 0;
} /* 去除两列之间的空白 */

.main>div,
.main>div>div {
border: 1px solid black;
} /* 令 class=main 下的子 div 和孙 div 都获得边框,便于观察 */

.left,
.right {
display: inline-block; /* 使得左右两列并排 */
width: 400px;
}

/* 之后在左右盒子中分别逐行排列即可 */

.large {
width: 400px;
height: 500px;
}

.middle {
width: 400px;
height: 300px;
}

.small {
width: 400px;
height: 200px;
}

效果如图

三栏布局

显然只需要在 body 中放置 3 个块

于是可以推出如下 HTML

1
2
3
4
5
<body>
<div class="left"></div>
<main></main>
<div class="right"></div>
</body>

使用如下 CSS,即可完成三栏布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
* {
margin: 0;
padding: 0;
box-sizing: border-box;
} /* reset */

body {
font-size: 0;
} /* 去除底部空白 */

body>* {
display: inline-block;
} /* 使得三个块并排 */

main {
border: 1px solid black;
height: 100vh;
width: 60vw;
} /* 使得 main 获得大小和边框,便于观察 */

/* 分别设定左右宽度 */

.left {
width: 20vw;
}

.right {
width: 20vw;
}

效果如图

可见随着视口变小,中间部分也会自适应

平均布局

平均布局一般都设定了每个单位所占大小,所以一般分为占满全行和占不满两种

而占不满的话,在多行场合下末行又会很难看,所以一般都是占满全行

设每个块大小为 width = height = 200px,每行 4 个块,放在刚才的三栏布局的 main 元素中

那么显然,为了维持每行 4 个块,应该取消响应式

鉴于之前说过的 margin 的同行不合并特性,一般只会设置单方向(通常为 right)的 margin

设每两个块之间隔开的距离为 x,显然 4 200px + 3 x = main.width - main.border.width * 2

设 x = 30px,则 main.width = 892px

可以推出如下 HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<body>
<div class="left"></div>
<main>
<div class="column">
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
</div>
<div class="column">
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
</div>
<div class="column">
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
</div>
</main>
<div class="right"></div>
</body>

使用以下 CSS,即可实现占满全行的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/* reset */

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* 去除底部空白 */

body {
font-size: 0;
}

/* 使得三个块并排 */

body>* {
display: inline-block;
}

/* 使得 main 获得大小和边框,便于观察 */

main {
border: 1px solid black;
height: 100vh;
width: 892px;
}

/* 分别设定左右宽度 */

.left {
width: calc((100vw - 892px) / 2);
}

.right {
width: calc((100vw - 892px) / 2);
}

.entity {
display: inline-block;
border: 1px solid black;
width: 200px;
height: 200px;
margin-right: 30px;
}

效果如图

发现惨了!怎么换行了!

从开发者工具可以看到,是因为每行最后一个 div 的右外边距超出容器了,所以被换行,这时候,我们需要添加如下 CSS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/* reset */

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* 去除底部空白 */

body {
font-size: 0;
}

/* 使得三个块并排 */

body>* {
display: inline-block;
}

/* 使得 main 获得大小和边框,便于观察 */

main {
border: 1px solid black;
height: 100vh;
width: 892px;
}

/* 分别设定左右宽度 */

.left {
width: calc((100vw - 892px) / 2);
}

.right {
width: calc((100vw - 892px) / 2);
}

.entity {
display: inline-block;
border: 1px solid black;
width: 200px;
height: 200px;
margin-right: 30px;
}

/* 选择第 4 个 entity div 并取消其右外边距 */

.column>.entity:nth-child(4) {
margin-right: 0;
}

或选用对容器进行调整的 CSS,更高级,更受面试官喜爱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/* reset */

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* 去除底部空白 */

body {
font-size: 0;
}

/* 使得三个块并排 */

body>* {
display: inline-block;
}

/* 使得 main 获得大小和边框,便于观察 */

main {
border: 1px solid black;
height: 100vh;
width: 892px;
}

/* 分别设定左右宽度 */

.left {
width: calc((100vw - 892px) / 2);
}

.right {
width: calc((100vw - 892px) / 2);
}

.entity {
display: inline-block;
border: 1px solid black;
width: 200px;
height: 200px;
margin-right: 30px;
}

/* 令行末的右外边距折返 30px */

.column {
margin-right: -30px;
}

效果如图

但是现在纯文档流布局已经没人用了,除非你的网站需要在 IE 6 以下运行……

float

通过 caniuse,我们知道对于 IE,float 可以运行在 IE 6 7 8 9 10 11 上,除非 6 以下才需要使用纯文档流布局

而 flex 在 IE 6 7 8 9 上都不能使用,10 和 11 的兼容性也不是完全兼容

所以,假如你还需要兼容 IE,最好使用 float 布局

两栏布局

由图片可得,大致有如下结构的 HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>
<div class="main clearfix">
<div class="left">
<div class="large"></div>
<div class="small"></div>
<div class="small"></div>
</div>
<div class="right">
<div class="middle"></div>
<div class="middle"></div>
<div class="middle"></div>
</div>
</div>
</body>

使用如下 CSS,即可完成两栏布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* reset */

.main {
font-size: 0;
}

/* 去除两列之间的空白 */

.main>div,
.main>div>div {
border: 1px solid black;
}

/* 令 class=main 下的子 div 和孙 div 都获得边框,便于观察 */

.left,
.right {
width: 400px;
}

/* 令 left 和 right 都向左浮动 */

.left {
float: left;
}

.right {
float: left;
}

/* 之后在左右盒子中分别逐行排列即可 */

.large {
width: 400px;
height: 500px;
}

.middle {
width: 400px;
height: 300px;
}

.small {
width: 400px;
height: 200px;
}

/* 用于清除浮动,否则会发现父元素的高度变成了 0 */

.clearfix:after{
content: '';
display: block;
clear: both;
}

效果图与纯文档流相同

从 CSS 可见,如果同层级的两个元素都是向左浮动的话,则排列顺序按照其在文档中的出现顺序

当然也可以令右边盒子向右浮动,此时右边盒子会出现在父元素的最右边,具体位置取决于父元素宽度

在本例中,如果父元素宽度为 800px,则与向左浮动表现相同

三栏布局

显然只需要在 body 中放置 3 个块

于是可以推出如下 HTML

1
2
3
4
5
<body class="clearfix">
<div class="left"></div>
<main></main>
<div class="right"></div>
</body>

使用如下 CSS,即可完成三栏布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/* reset */

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* 使得 main 获得大小和边框,便于观察 */

main {
float: left;
border: 1px solid black;
height: 100vh;
width: 60vw;
}

.left {
float: left;
border: 1px solid white;
/* 若不设定边框,则会观察到左右空白的宽度都变为 0,但可以设置白色边框保持左边空白 */
width: 20vw;
}

.right {
float: right;
/* 右边不需要设置边框,因为排列是从左向右的,除非 main 的 float = right */
width: 20vw;
}

/* 用于清除浮动,否则会发现父元素的高度变成了 0 */

.clearfix:after {
content: '';
display: block;
clear: both;
}

效果图与纯文档流相同

平均布局

float 实现平均布局的代码和效果,与纯文档流几乎一致,除了 clearfix,就只是把

1
display: inline-block;

换成了

1
float: left;

而已

关于右外边距溢出容器的处理,也与纯文档流一致

故此处不再给出示例

flex

通过 caniuse 可以发现,除了 IE 之外的所有浏览器最新版本均支持 flex,但 grid 布局尚未受到全面支持

所以当你不需要兼容 IE,但又不是只需要兼容最新的浏览器时,可以选择 flex 布局方案

附一个 flex 布局小游戏

FLEXBOX FROGGY

语法

Flex 布局教程:语法篇)

flex 有两个主要概念:容器(container)和项目(item)

一个容器中包含两条轴:主轴和交叉轴,交叉轴恒垂直于主轴。默认主轴是水平方向

一个容器中往往包含多个项目,项目沿主轴排列

容器

容器上可以设置 6 个属性

  • flex-direction
  • flex-wrap
  • flex-flow
  • justify-content
  • align-items
  • align-content

属性含义不解释了,本文不是教语法的,是教布局的,可以点击阮一峰博客学习语法

下同,不讲语法

项目

项目上可以设置 6 个属性

  • order
  • flex-grow
  • flex-shrink
  • flex-basis
  • flex
  • align-self

两栏布局

显然扭转主轴方向到竖直方向,更有利于排版

所以由图片可得,大致有如下结构的 HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<body>
<div class="main">
<div class="left">
<div class="large"></div>
<div class="small"></div>
<div class="small"></div>
</div>
<div class="right">
<div class="middle"></div>
<div class="middle"></div>
<div class="middle"></div>
</div>
</div>
</body>

使用如下 CSS,即可完成两栏布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/* reset */

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* 令 class=main 下的子 div 和孙 div 都获得边框,便于观察 */

.main>div,
.main>div>div {
border: 1px solid black;
}

/* 令主盒子成为弹性盒子容器,使得左右盒子沿默认主轴(水平方向)排列 */

.main {
display: flex;
}

.left,
.right {
/* 令左右盒子都成为弹性盒子容器 */
display: flex;
/* 更改主轴方向 */
flex-direction: column;
width: 400px;
height: 800px;
}

/* 之后在左右盒子中分别设置大小即可 */

.large {
flex-grow: 2;
}

.middle {
flex-grow: 1;
}

.small {
flex-grow: 1;
}

效果图与纯文档流相同

三栏布局

显然只需要在 body 中放置 3 个块

于是可以推出如下 HTML

1
2
3
4
5
<body>
<div class="left"></div>
<main></main>
<div class="right"></div>
</body>

使用如下 CSS,即可完成三栏布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/* reset */

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* 使得 body 成为弹性盒子容器,让三个区域都沿默认主轴(水平方向)排列 */

body {
display: flex;
height: 100vh;
}

/* 使得 main 获得大小和边框,便于观察 */

main {
border: 1px solid black;
flex-grow: 3;
}

.left {
flex-grow: 1;
}

.right {
flex-grow: 1;
}

效果图与纯文档流相同

代码明显比 float 简洁许多,所以是比 float 更好用的布局方案

平均布局

设每个块大小为 width = height = 200px,每行 4 个块,放在刚才的三栏布局的 main 元素中

可以推出如下 HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<body>
<div class="left"></div>
<main>
<div class="column">
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
</div>
<div class="column">
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
</div>
<div class="column">
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
</div>
</main>
<div class="right"></div>
</body>

使用以下 CSS,即可实现占满全行的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/* reset */

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* 使得 body 成为弹性盒子容器,让三个区域都沿默认主轴(水平方向)排列 */

body {
display: flex;
height: 100vh;
}

/* 使得 main 获得大小和边框,便于观察 */

main {
border: 1px solid black;
flex-grow: 3;
}

.left {
flex-grow: 1;
}

.right {
flex-grow: 1;
}

.column {
border: 1px solid black;
display: flex;
max-height: 200px;
height: 15vw;
justify-content: space-between;
}

.entity {
border: 1px solid black;
width: 200px;
height: 200px;
}

效果如图

发现,坏了,怎么最后一行隔开这么远

于是我们便不能使用 justify-content 来简单控制间距,必须使用 margin-right 来控制

计算结果沿用纯文档流布局的计算结果

于是 CSS 变更为如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/* reset */

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* 去除底部空白 */

body {
font-size: 0;
}

/* 使得三个块并排 */

body>* {
display: inline-block;
}

/* 使得 main 获得大小和边框,便于观察 */

main {
border: 1px solid black;
height: 100vh;
width: 892px;
}

/* 分别设定左右宽度 */

.left {
width: calc((100vw - 892px) / 2);
}

.right {
width: calc((100vw - 892px) / 2);
}

/* 令行末的右外边距折返 30px */

.column {
display: flex;
margin-right: -30px;
}

.entity {
border: 1px solid black;
width: 200px;
height: 200px;
margin-right: 30px;
}

效果如图

好了,使用负 margin,成功修复!

当然 nth-child 也可以,不过不建议

grid

通过 caniuse 可以发现,grid 布局尚未受到全面支持

所以当你只需要兼容最新的,甚至是未来的浏览器时,可以选择 grid 布局方案

附一个 grid 布局小游戏

GRID GARDEN

语法

CSS Grid 网格布局教程

容器和项目的概念同 flex,但是 grid 没有主轴和交叉轴

如果说 flex 的精力集中在每个容器的主轴上,是一维操作,那 grid 布局就是二维操作

容器

容器上可以设置 7 种属性

  • grid-template-columns/rows/areas

  • column/row-gap

  • gap

  • grid-auto-flow

  • justify/align/place-items

  • justify/align/place-content

  • grid-auto-columns/rows

项目

项目上可以设置 3 种属性

  • grid-column/row-start/end
  • grid-column/row/area
  • justify/align/place-self

两栏布局

由图片可得,大致有如下结构的 HTML

1
2
3
4
5
6
7
8
9
10
<body>
<div class="main">
<div class="large"></div>
<div class="small1"></div>
<div class="small2"></div>
<div class="middle1"></div>
<div class="middle2"></div>
<div class="middle3"></div>
</div>
</body>

使用如下 CSS,即可完成两栏布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/* reset */

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* 令 class=main 下的子 div 和孙 div 都获得边框,便于观察 */

.main>div,
.main>div>div {
border: 1px solid black;
}

/* 令主盒子成为 grid 容器 */

.main {
display: grid;
grid-template-columns: 400px 400px;
grid-template-rows: repeat(9, 100px);
grid-template-areas: 'large middle1'
'large middle1'
'large middle1'
'large middle2'
'large middle2'
'small1 middle2'
'small1 middle3'
'small2 middle3'
'small2 middle3';
}

.large {
grid-area: large;
}

.small1 {
grid-area: small1;
}

.small2 {
grid-area: small2;
}

.middle1 {
grid-area: middle1;
}

.middle2 {
grid-area: middle2;
}

.middle3 {
grid-area: middle3;
}

效果图与纯文档流相同

语义化非常好,一看就知道布局长什么样

三栏布局

显然只需要在 body 中放置 3 个块

于是可以推出如下 HTML

1
2
3
4
5
<body>
<div class="left"></div>
<div class="center"></div>
<div class="right"></div>
</body>

使用如下 CSS,即可完成三栏布局

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* reset */

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* 令 div 都获得边框,便于观察 */

div {
border: 1px solid black;
}

/* 令主盒子成为 grid 容器 */

body {
font-size: 0;
display: grid;
grid-template-columns: 1fr 3fr 1fr;
grid-template-rows: 100vh;
grid-template-areas: 'left center right';
}

.left {
grid-area: left;
}

.center {
grid-area: center;
}

.right {
grid-area: right;
}

效果如下

这令人惊叹的高度语义化,比 flex 高到不知道哪去了

平均布局

设每个块大小为 width = height = 200px,每行 4 个块,放在刚才的三栏布局的 main 元素中

可以推出如下 HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body>
<div class="left"></div>
<div class="center">
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
<div class="entity"></div>
</div>
<div class="right"></div>
</body>

使用以下 CSS,即可实现占满全行的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/* reset */

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* 令 div 都获得边框,便于观察 */

div {
border: 1px solid black;
}

/* 令主盒子成为 grid 容器 */

body {
font-size: 0;
display: grid;
grid-template-columns: 1fr 3fr 1fr;
grid-template-rows: 100vh;
grid-template-areas: 'left center right';
}

.left {
grid-area: left;
}

.center {
grid-area: center;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(auto-fill, 200px);
}

.right {
grid-area: right;
}

效果如图

完美的自动占格子,不再有负 margin 的问题

即便需要间隔,也可以通过 gap 来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/* reset */

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

/* 令 div 都获得边框,便于观察 */

div {
border: 1px solid black;
}

/* 令主盒子成为 grid 容器 */

body {
font-size: 0;
display: grid;
grid-template-columns: 1fr 3fr 1fr;
grid-template-rows: 100vh;
grid-template-areas: 'left center right';
}

.left {
grid-area: left;
}

.center {
grid-area: center;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(auto-fill, 200px);
column-gap: 30px;
row-gap: 10px;
}

.right {
grid-area: right;
}

效果如图

天不生我 grid 布局,万古如长夜!!!

--It's the end.Thanks for your read.--