CSS 布局

CSS 布局

布局

CSS 页面布局技术允许我们拾取网页中的元素,并且控制它们相对正常布局流、周边元素、父容器或者主视口/窗口的位置。在这个模块中将涉及更多关于页面布局技术的细节:

  • 正常布局流
  • display属性
  • 弹性盒子
  • 网格
  • 浮动
  • 定位
  • CSS 表格布局
  • 多列布局

每种技术都有它们的用途,各有优缺点,相互辅助。通过理解各个布局方法的设计理念,你能够找到构建你想要的网页需要的布局方案。

正常布局流

正常布局流(normal flow)是指在不对页面进行任何布局控制时,浏览器默认的 HTML 布局方式。

下列布局技术会覆盖默认的布局行为:

  • display 属性 — 标准的 value,比如block, inline 或者 inline-block 元素在正常布局流中的表现形式。 接着是全新的布局方式,通过设置display的值,比如 CSS Grid 和 Flexbox.
  • 浮动——应用 float 值,诸如 left 能够让块级元素互相并排成一行,而不是一个堆叠在另一个上面。
  • position 属性 — 允许你精准设置盒子中的盒子的位置,正常布局流中,默认为 static ,使用其他值会引起元素不同的布局方式,例如将元素固定到浏览器视口的左上角。
  • 表格布局— 表格的布局方式可以用在非表格内容上,可以使用display: table和相关属性在非表元素上使用。
  • 多列布局— 这个 Multi-column layout 属性可以让块按列布局,比如报纸的内容就是一列一列排布的。

display 属性

在 css 中实现页面布局的主要方法是设定display属性的值。此属性允许我们更改默认的显示方式。正常流中的所有内容都有一个display的值,用作元素的默认行为方式。

在讨论布局时,对我们来说最重要的两个值是 display: flex 和 display: grid。

弹性盒子

Flexbox 是 CSS 弹性盒子布局模块(Flexible Box Layout Module)的缩写,它被专门设计出来用于创建横向或是纵向的一维页面布局。要使用 flexbox,你只需要在想要进行 flex 布局的父元素上应用display: flex ,所有直接子元素都将会按照 flex 进行布局。

当我们把display: flex添加到它的父元素时,元素就自动按列进行排列。这是由于它们变成了flex 项 (flex items),按照 flex 容器(也就是它们的父元素)的一些 flex 相关的初值进行 flex 布局:它们整整齐齐排成一行,是因为父元素上flex-direction的初值是row。它们全都被拉伸至和最高的元素高度相同,是因为父元素上align-items属性的初值是stretch

flex 模型说明:

当元素表现为 flex 框时,它们沿着两个轴来布局:

  • 主轴(main axis)是沿着 flex 元素放置的方向延伸的轴(比如页面上的横向的行、纵向的列)。该轴的开始和结束被称为 main startmain end
  • 交叉轴(cross axis)是垂直于 flex 元素放置方向的轴。该轴的开始和结束被称为 cross startcross end
  • 设置了 display: flex 的父元素被称之为 flex 容器(flex container)。
  • 在 flex 容器中表现为弹性的盒子的元素被称之为 flex 项flex item)。

弹性盒子提供了 flex-direction 这样一个属性,它可以指定主轴的方向(弹性盒子子类放置的地方)——它默认值是 row,这使得它们在按你浏览器的默认语言方向排成一排(在英语/中文浏览器中是从左到右)。flex-direction:column是垂直排列。

还可以使用 row-reverse 和 column-reverse 值反向排列 flex 项。

换行:flex-wrap:wrap

flex-flow 缩写:

flex-direction 和 flex-wrap——的缩写 flex-flow

1
2
3
4
flex-direction: row;
flex-wrap: wrap;

flex-flow: row wrap;

flex 项的动态尺寸:

本例中,我们设置 <article> 元素的 flex 值为 1,这表示每个元素占用空间都是相等的,占用的空间是在设置 padding 和 margin 之后剩余的空间。因为它是一个比例,这意味着将每个 flex 项的设置为 400000 的效果和 1 的时候是完全一样的。

第三个 <article> 元素占用了两倍的可用宽度,剩下的一样——现在总共有四个比例单位可用。前两个 flex 项各有一个,因此它们占用每个可用空间的 1/4。第三个有两个单位,所以它占用 2/4 或者说是 1/2 的可用空间。

1
2
3
4
5
6
7
article {
flex: 1;
}

article:nth-of-type(3) {
flex: 2;
}

这是一个无单位的比例值,表示每个 flex 项沿主轴的可用空间大小。

还可以指定 flex 的最小值。

1
2
3
4
5
6
7
article {
flex: 1 200px;
}

article:nth-of-type(3) {
flex: 2 200px;
}

水平和垂直对齐:

align-items 控制 flex 项在交叉轴上的位置。

  • 默认的值是 stretch,其会使所有 flex 项沿着交叉轴的方向拉伸以填充父容器。如果父容器在交叉轴方向上没有固定宽度(即高度),则所有 flex 项将变得与最长的 flex 项一样长(即高度保持一致)。
  • 在上面规则中我们使用的 center 值会使这些项保持其原有的高度,但是会在交叉轴居中。这就是那些按钮垂直居中的原因。
  • 你也可以设置诸如 flex-start 或 flex-end 这样使 flex 项在交叉轴的开始或结束处对齐所有的值。
1
2
3
4
5
div {
display: flex;
align-items: center;
justify-content: space-around;
}

可以用 align-self 属性覆盖 align-items 的行为。

1
2
3
button:first-child {
align-self: flex-end;
}

justify-content 控制 flex 项在主轴上的位置。

  • 默认值是 flex-start,这会使所有 flex 项都位于主轴的开始处。
  • 你也可以用 flex-end 来让 flex 项到结尾处。
  • center 在 justify-content 里也是可用的,可以让 flex 项在主轴居中。
  • 而我们上面用到的值 space-around 是很有用的——它会使所有 flex 项沿着主轴均匀地分布,在任意一端都会留有一点空间。
  • 还有一个值是 space-between,它和 space-around 非常相似,只是它不会在两端留下任何空间。

flex 项排序:

弹性盒子也有可以改变 flex 项的布局位置的功能,而不会影响到源顺序(即 dom 树里元素的顺序)。

1
2
3
button:first-child {
order: 1;
}

刷新下,然后你会看到“Smile”按钮移动到了主轴的末尾。下面我们谈下它实现的一些细节:

  • 所有 flex 项默认的 order 值是 0
  • order 值大的 flex 项比 order 值小的在显示顺序中更靠后。
  • 相同 order 值的 flex 项按源顺序显示。所以假如你有四个元素,其 order 值分别是 2,1,1 和 0,那么它们的显示顺序就分别是第四,第二,第三,和第一。
  • 第三个元素显示在第二个后面是因为它们的 order 值一样,且第三个元素在源顺序中排在第二个后面。

你也可以给 order 设置负值使它们比值为 0 的元素排得更前面。比如,你可以设置“Blush”按钮排在主轴的最前面:

1
2
3
button:last-child {
order: -1;
}

flex 嵌套:

弹性盒子也能创建一些颇为复杂的布局。设置一个元素为 flex 项,那么他同样成为一个 flex 容器,它的孩子(直接子节点)也表现为弹性盒子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<section>
<article>
<h2>First article</h2>
<p>Tacos actually microdosing, pour-over semiotics banjo chicharrones retro fanny pack portland everyday carry vinyl typewriter. Tacos PBR&B pork belly, everyday carry ennui pickled sriracha normcore hashtag polaroid single-origin coffee cold-pressed. PBR&B tattooed trust fund twee, leggings salvia iPhone photo booth health goth gastropub hammock.</p>
</article>

<article>
<article>
<h2>Third article</h2>
<p>Tacos actually microdosing, pour-over semiotics banjo chicharrones retro fanny pack portland everyday carry vinyl typewriter. Tacos PBR&B pork belly, everyday carry ennui pickled sriracha normcore hashtag polaroid single-origin coffee cold-pressed. PBR&B tattooed trust fund twee, leggings salvia iPhone photo booth health goth gastropub hammock.</p>
<p>Cray food truck brunch, XOXO +1 keffiyeh pickled chambray waistcoat ennui. Organic small batch paleo 8-bit. Intelligentsia umami wayfarers pickled, asymmetrical kombucha letterpress kitsch leggings cold-pressed squid chartreuse put a bird on it. Listicle pickled man bun cornhole heirloom art party.</p>
</article>
<p>Tacos actually microdosing, pour-over semiotics banjo chicharrones retro fanny pack portland everyday carry vinyl typewriter. Tacos PBR&B pork belly, everyday carry ennui pickled sriracha normcore hashtag polaroid single-origin coffee cold-pressed. PBR&B tattooed trust fund twee, leggings salvia iPhone photo booth health goth gastropub hammock.</p>
</article>
</section>
1
2
3
4
5
6
7
section {
display: flex;
}

article:nth-of-type(2) {
display: flex;
}

Grid 布局

Flexbox 用于设计横向或纵向的布局,而 Grid 布局则被设计用于同时在两个维度上把元素按行和列排列整齐。

使用display:grid,分别使用 grid-template-rows 和 grid-template-columns 两个属性定义了一些行和列的轨道。

利用 grid-column 和 grid-row 两个属性来指定每一个子元素应该从哪一行/列开始,并在哪一行/列结束。这就能够让子元素在多个行/列上展开。

什么是网格布局?

网格是由一系列水平及垂直的线构成的一种布局模式。根据网格,我们能够将设计元素进行排列,帮助我们设计一系列具有固定位置以及宽度的元素的页面,使我们的网站页面更加统一。

一个网格通常具有许多的列(column)与行(row),以及行与行、列与列之间的间隙,这个间隙一般被称为沟槽(gutter)

定义一个网格:

默认情况下,子项按照正常布局流自顶而下排布。

通过把容器的 display 属性设置为 grid ,来定义一个网格。

与弹性盒子一样,将父容器改为网格布局后,他的直接子项会变为网格项。

与弹性盒子不同的是,在定义网格后,网页并不会马上发生变化。因为 display: grid 的声明只创建了一个只有一列的网格,所以子项还是会像正常布局流那样,自上而下、一个接一个的排布。

1
2
3
.container {
display: grid;
}

为了让我们的容器看起来更像一个网格,我们要给刚定义的网格加一些列。那就让我们加三个宽度为200px的列。当然,这里可以用任何长度单位,包括百分比。

1
2
3
4
.container {
display: grid;
grid-template-columns: 200px 200px 200px;
}

使用 fr 单位的灵活网格:

除了长度和百分比,我们也可以用 fr 这个单位来灵活地定义网格的行与列的大小。这个单位代表网格容器中可用空间的一份。

将窗口调窄(由于示例中设定了max-width,可能需要很窄),你应该能看到每一列的宽度可以会随着可用空间变小而变小。fr 单位按比例划分了可用空间。

这个定义里,第一列被分配了 2fr 可用空间,余下的两列各被分配了 1fr 的可用空间,这会使得第一列的宽度更大。另外,fr可以与一般的长度单位混合使用。比如设置 grid-template-columns: 300px 2fr 1fr,那么第一列宽度是300px,剩下的两列会根据剩余的可用空间按比例分配。

1
2
3
4
.container {
display: grid;
grid-template-columns: 2fr 1fr 1fr;
}

fr单位分配的是可用空间而非所有空间,所以如果某一格包含的内容变多了,那么整个可用空间就会减少,可用空间是不包括那些已经确定被占用的空间的。

网格间隙:

使用 grid-column-gap 属性来定义列间隙;使用 grid-row-gap来定义行间隙;使用 grid-gap 可以同时设定两者。

间隙距离可以用任何长度单位包括百分比来表示,但不能使用fr单位

gap属性曾经有一个grid-前缀,不过后来的标准进行了修改,目的是让他们能够在不同的布局方法中都能起作用。尽管现在这个前缀不会影响语义,但为了代码的健壮性,你可以把两个属性都写上。

1
2
3
4
5
6
.container {
display: grid;
grid-template-columns: 2fr 1fr 1fr;
grid-gap: 20px;
gap: 20px;
}

重复构建轨道组:

可以使用repeat来重复构建具有某些宽度配置的某些列。举个例子,如果要创建多个等宽轨道,可以用下面的方法。

1
2
3
4
5
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 20px;
}

和之前一样,你仍然得到了 3 个 1fr 的列。第一个传入 repeat 函数的值3表明了后续列宽的配置要重复多少次,而第二个值1fr表示需要重复的构建配置,这个配置可以具有多个长度设定。例如repeat(2, 2fr 1fr),这相当于填入了 2fr 1fr 2fr 1fr。

关键字auto-fill可以确定的重复次数。

1
2
3
4
5
6
repeat(auto-fill, 250px)
repeat(auto-fit, 250px)
repeat(auto-fill, [col-start] 250px [col-end])
repeat(auto-fit, [col-start] 250px [col-end])
repeat(auto-fill, [col-start] minmax(100px, 1fr) [col-end])
repeat(auto-fill, 10px [col-start] 30% [col-middle] 400px [col-end])

显式网格与隐式网格:

到目前为止,我们定义过了列,但还没有管过行。但在这之前,我们要来理解一下显式网格和隐式网格。显式网格是我们用 grid-template-columns 或 grid-template-rows 属性创建的。而隐式网格则是当有内容被放到网格外时才会生成的。显式网格与隐式网格的关系与弹性盒子的 main 和 cross 轴的关系有些类似。

隐式网格中生成的行/列大小是参数默认是 auto ,大小会根据放入的内容自动调整。当然,你也可以使用grid-auto-rows和grid-auto-columns属性手动设定隐式网格轨道的大小。下面的例子将grid-auto-rows设为了100px,然后你可以看到那些隐式网格中的行(因为这个例子里没有设定grid-template-rows,因此,所有行都位于隐式网格内)现在都是 100 像素高了。

1
2
3
4
5
6
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 100px;
grid-gap: 20px;
}

minmax() 函数:

很难知道网页上某个元素的尺寸在不同情况下会变成多少,一些额外的内容或者更大的字号就会导致许多能做到像素级精准的设计出现问题。所以,我们有了minmax()函数。

minmax() 函数为一个行/列的尺寸设置了取值范围。比如设定为 minmax(100px, auto),那么尺寸就至少为 100 像素,并且如果内容尺寸大于 100 像素则会根据内容自动调整。在这里试一下把 grid-auto-rows 属性设置为minmax函数。

1
2
3
4
5
6
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: minmax(100px, auto);
grid-gap: 20px;
}

如果所有网格内的内容均小于 100 像素,看起来不会有变化。

但如果在某一项中放入很长的内容或者图片,你可以看到这个格子所在的哪一行的高度变成能刚好容纳内容的高度了

自动使用多列填充:

用到 repeat() 函数中的一个关键字auto-fill来替代确定的重复次数。而函数的第二个参数,我们使用minmax()函数来设定一个行/列的最小值,以及最大值 1fr。

1
2
3
4
5
6
.container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
grid-auto-rows: minmax(100px, auto);
grid-gap: 20px;
}

可以看到形成了一个包含了许多至少 200 像素宽的列的网格,将容器填满。随着容器宽度的改变,网格会自动根据容器宽度进行调整,每一列的宽度总是大于 200 像素,并且容器总会被列填满

基于线的元素放置

在定义完了网格之后,我们要把元素放入网格中。我们的网格有许多分隔线,第一条线的起始点与文档书写模式相关。在英文中第一条列分隔线(即网格边缘线)在网格的最左边第一条行分隔线在网格的最上面。而对于阿拉伯语,第一条列分隔线在网格的最右边,因为阿拉伯文是从右往左书写的。

我们根据这些分隔线来放置元素,通过以下属性来指定从那条线开始到哪条线结束。

  • grid-column-start(en-US)
  • grid-column-end(en-US)
  • grid-row-start(en-US)
  • grid-row-end(en-US)

这些属性的值均为分隔线序号,你也可以用以下缩写形式来同时指定开始与结束的线。

  • grid-column
  • grid-row

注意开始与结束的线的序号要使用/符号分开。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
header {
grid-column: 1 / 3;
grid-row: 1;
}

article {
grid-column: 2;
grid-row: 2;
}

aside {
grid-column: 1;
grid-row: 2;
}

footer {
grid-column: 1 / 3;
grid-row: 3;
}

也可以用 -1 来定位到最后一条列分隔线或是行分隔线,并且可以用负数来指定倒数的某一条分隔线。但是这只能用于显式网格,对于隐式网格-1不一定能定位到最后一条分隔线。

使用 grid-template-areas 属性放置元素:

另一种往网格放元素的方式是用grid-template-areas属性,并且你要命名一些元素并在属性中使用这些名字作为一个区域。

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
.container {
display: grid;
grid-template-areas:
"header header"
"sidebar content"
"footer footer";
grid-template-columns: 1fr 3fr;
gap: 20px;
}

header {
grid-area: header;
}

article {
grid-area: content;
}

aside {
grid-area: sidebar;
}

footer {
grid-area: footer;
}

grid-template-areas属性的使用规则如下:

  • 你需要填满网格的每个格子
  • 对于某个横跨多个格子的元素,重复写上那个元素grid-area属性定义的区域名字
  • 所有名字只能出现在一个连续的区域,不能在不同的位置出现
  • 一个连续的区域必须是一个矩形
  • 使用.符号,让一个格子留空

浮动

把一个元素“浮动”(float) 起来,会改变该元素本身和在正常布局流(normal flow)中跟随它的其他元素的行为。这一元素会浮动到左侧或右侧,并且从正常布局流 (normal flow) 中移除,这时候其他的周围内容就会在这个被设置浮动 (float) 的元素周围环绕。

当一个元素被浮动后,它会像一个盒子一样,从文档流中移动到其容器的一边。其他元素会认为浮动的元素所占据的空间仍然存在,因此会在布局时避开这个空间。

float 属性有四个可能的值:

  • left — 将元素浮动到左侧。
  • right — 将元素浮动到右侧。
  • none — 默认值,不浮动。
  • inherit — 继承父元素的浮动属性。

例子当中,我们把一个

元素浮动到左侧,并且给了他一个右侧的margin,把文字推开。这给了我们文字环绕着这个
元素的效果,在现代网页设计当中,这是你唯一需要学会的事情。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<h1>Simple float example</h1>

<div class="box">Float</div>

<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam
dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus
ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus
laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum,
tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus
neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat
volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros
pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec
lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien.
</p>
1
2
3
4
5
6
.box {
float: left;
width: 150px;
height: 150px;
margin-right: 30px;
}

清除浮动

如果不想让剩余元素也受到浮动元素的影响,我们需要停止它;这是通过添加 clear 属性实现的。

1
2
3
.cleared {
clear: left;
}

应该看到,第二个段落已经停止了浮动,不会再跟随浮动元素排布了。clear 属性接受下列值:

  • left:停止任何活动的左浮动
  • right:停止任何活动的右浮动
  • both:停止任何活动的左右浮动

清除浮动元素周围的盒子

当浮动元素脱离正常文档流时,它会不再占据文档流中的空间,而是漂浮在文档的特定位置。当你想要一个盒子(容器)包含浮动元素并与浮动元素后续内容产生正常的布局时,通常需要处理浮动元素的影响。这涉及到清除浮动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div class="wrapper">
<div class="box">Float</div>
<p class="hhh">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam
dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus
ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus
laoreet sit amet.
</p>
</div>
<p>
Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet
orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare
ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse
ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis
ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et
a urna. Ut id ornare felis, eget fermentum sapien.
</p>

如果你给 .float-left 元素应用了 float: left;,它会脱离正常文档流,而 .container 的高度不会自动包含浮动元素。这可能导致 .container高度坍缩,从而影响下方紧随其后的内容。

有三种方法可以处理这个问题:
clearfix:

先向包含浮动内容及其本身的盒子后方插入一些生成的内容,并将生成的内容清除浮动。

1
2
3
4
5
.wrapper::after {
content: "";
clear: both;
display: block;
}

使用 overflow:

一个替代的方案是将包裹元素的 overflow 属性设置为除 visible 外的其他值。在包裹元素上添加 overflow: auto 规则。现在,盒子应该再一次停止浮动。

1
2
3
4
5
6
.wrapper {
background-color: rgb(79, 185, 227);
padding: 10px;
color: #fff;
overflow: auto;
}

这个例子之所以能够生效,是因为创建了所谓的 块格式化上下文(BFC)。可以把它看作页面内部包含所需元素的一小块布局区域。如此设置可以让浮动元素包含在 BFC 及其背景之内。大部分情况下这种小技巧都可以奏效,但是可能会出现莫名其妙的滚动条或裁剪阴影,这是使用 overflow 带来的一些副作用

display: flow-root:

一个较为现代的方案是使用 display 属性的 flow-root 值。它可以无需小技巧来创建块格式化上下文(BFC),在使用上没有副作用。

1
2
3
4
5
6
.wrapper {
background-color: rgb(79, 185, 227);
padding: 10px;
color: #fff;
display: flow-root;
}

定位技术

定位 (positioning) 能够让我们把一个元素从它原本在正常布局流 (normal flow) 中应该在的位置移动到另一个位置。定位 (positioning) 并不是一种用来给你做主要页面布局的方式,它更像是让你去管理和微调页面中的一个特殊项的位置。

有一些非常有用的技术在特定的布局下依赖于position属性。同时,理解定位 (positioning) 也能够帮助你理解正常布局流 (normal flow),理解把一个元素移出正常布局流 (normal flow) 是怎么一回事。

五种主要的定位类型需要我们了解:

  • 静态定位(Static positioning)是每个元素默认的属性——它表示“将元素放在文档布局流的默认位置——没有什么特殊的地方”。
1
2
3
.positioned {
position: static;
}
  • 相对定位(Relative positioning)允许我们相对于元素在正常的文档流中的位置移动它——包括将两个元素叠放在页面上。这对于微调和精准设计(design pinpointing)非常有用。
1
position: relative;

  • 绝对定位(Absolute positioning)将元素完全从页面的正常布局流(normal layout flow)中移出,类似将它单独放在一个图层中。我们可以将元素相对于页面的 元素边缘固定,或者相对于该元素的最近被定位祖先元素*(nearest positioned ancestor element)。绝对定位在创建复杂布局效果时非常有用,例如通过标签显示和隐藏的内容面板或者通过按钮控制滑动到屏幕中的信息面板。将元素从文档流 (document flow) 当中移出了。*
1
position: absolute;

定位上下文:可以改变定位上下文 —— 绝对定位的元素的相对位置元素。通过设置其中一个父元素的定位属性 —— 也就是包含绝对定位元素的那个元素(如果要设置绝对定位元素的相对元素,那么这个元素一定要包含绝对定位元素)。

z-index:可以更改堆叠顺序,网页也有一个 z 轴:一条从屏幕表面到你的脸(或者在屏幕前面你喜欢的任何其他东西)的虚线。z-index 值影响定位元素位于该轴上的位置;正值将它们移动到堆栈上方,负值将它们向下移动到堆栈中。默认情况下,定位的元素都具有 z-index 为 auto,实际上为 0。z-index 只接受无单位索引值,较高的值将高于较低的值。

  • 固定定位(Fixed positioning)与绝对定位非常类似,但是它是将一个元素相对浏览器视口固定,而不是相对另外一个元素。这在创建类似在整个页面滚动过程中总是处于屏幕的某个位置的导航菜单时非常有用。将元素从文档流 (document flow) 当中移出了。
1
position: fixed;
  • 粘性定位(Sticky positioning)是一种新的定位方式,它会让元素先保持和 position: static 一样的定位,当它的相对视口位置(offset from the viewport)达到某一个预设值时,它就会像 position: fixed 一样定位。
1
position: sticky;

表格布局

一个<table>标签之所以能够像表格那样展示,是由于 css 默认给<table>标签设置了一组 table 布局属性。当这些属性被应用于排列非<table>元素时,这种用法被称为“使用 CSS 表格”。

表布局是不灵活的,繁重的标记,难以调试和语义上的错误。

1
display: table;

多列布局

多列布局模组给了我们 一种把内容按列排序的方式,就像文本在报纸上排列那样。

要把一个块转变成多列容器 (multicol container),我们可以使用 column-count属性来告诉浏览器我们需要多少列。也可以使用column-width来告诉浏览器以至少某个宽度的尽可能多的列来填充容器,任何剩余的空间之后会被现有的列平分。这意味着你可能无法期望得到你指定宽度,除非容器的宽度刚好可以被你指定的宽度除尽。

1
2
3
4
5
6
.container {
column-count: 3;
}
.container {
column-width: 200px;
}
  • 使用 column-gap 改变列间间隙
  • 用 column-rule 在列间加入一条分割线,column-rule 是 column-rule-colorcolumn-rule-style的缩写,接受同 border 一样的单位。
  • break-inside属性来控制 multicol 和多页媒体中的内容拆分、折断。

传统的布局方法

CSS 网格布局之前的布局与网格系统

可以了解到基于 float 和 flexbox 的网格系统和网格框架是如何工作的。它们里面没有哪个的建立方式是像通过 CSS 网格布局创建网格那样,真的建立一个网格。他们通过给目标一个大小,然后推动它们,让它们看起来像网格一样排列成一条线。

两列布局

body 将会占据 90% 的视口宽度,直到达到 900 像素,在这种情况下,它将固定并保持在视口正中。

1
2
3
4
5
body {
width: 90%;
max-width: 900px;
margin: 0 auto;
}

如果希望两个<div>,一个浮在窗口的一边,另一个浮动在另一边的话,需要将它们的宽度设置为其父元素的 100% 或者更小,以便他们可以并排放置。

这里我们将它们都设置为了父元素宽度的 48%——总共是 96%,在两栏之间留 4% 的空隙,为它们提供一些宽松的空间。

1
2
3
4
5
6
7
8
9
div:nth-of-type(1) {
width: 48%;
float: left;
}

div:nth-of-type(2) {
width: 48%;
float: right;
}

在宽度的表示上都用的是百分比——这是一个很好的策略,这创建了一个流动布局(liquid layout),能够适应不同的屏幕大小,在小一些的屏幕上也能使列保持一样的比例。

浮动网格的限制

当你想用这个网格系统时,你得仔细看看你的总长是否正确,并且每行中的元素所横跨的列数不超过这一行可容纳的列数。由于浮动布局实现的方式,如果网格列的数目对与网格来说太大,在最后边的元素会跑到下一行去,搞坏了布局

还要记住,如果元素内容比行宽,它会溢出,看起来一团糟。

这个系统的最大限制是,它本质上是一维的。我们在处理列、让元素跨越列,但是处理不了行。如果不设置一个确定的高度,用老方法很难控制元素高。这个方法很不灵活 —它只有在你确定你的内容有个明确的高的情况下有用。

弹性盒网格

弹性布局不是为网格布局而设的,把它当网格布局来用也有新的挑战。

在顶行,我们有十二个整齐的盒子,它们在视口宽度改变时同等地放大缩小。可是在下一行,我们只有四个物件,它们也从六十像素的基础上变大变小。因为它们只有四个,它们可以长得比上一行的物件更快,结果是它们都占据了第二行的相同宽度。

为了解决这个问题,我们仍然需要包含span类,以提供一个用于那个元素的,可以替换掉为 flex-basis所使用的值的宽度。

弹性盒设计上是一维。它处理单个维度,行的或者列的。我们不能创建一个对行列严格要求的网格,意即如果我们要在我们的网格上使用弹性盒的话,我们仍然需要计算浮动布局的百分比。

第三方网格系统

流行的框架,例如Bootstrap和Foundation和Skeleton,就包含了网格系统。

Skeleton(或者其他任何网格框架)正在做的所有事情是,设定一个预定义的类,可以通过把它们加到你的标记文件里面的方式使用这些框架,和你自己做计算这些百分数的工作完全是一样的。

正如你所看到的这样,使用 Skeleton 的时候,几乎不需要写多少 CSS。向标记文本里面加类的时候,替我们处理了所有的浮动。

响应式设计

如果用户正在使用比设计者考虑到的更小或者更大的屏幕,那么结果从多余的滚动条,到过长的行和没有被合理利用的空间,不一而足。随着人们使用的屏幕尺寸的种类越来越多,出现了响应式网页设计的概念(responsive web design,RWD),RWD 指的是允许 Web 页面适应不同屏幕宽度因素等,进行布局和外观的调整的一系列实践。这是改变我们设计多设备网页的方式的思想。

响应式设计是将三种技术的混合使用:

  • 第一个是液态网格。
  • 第二个是液态图像的理念,通过使用相当简单的将设置max-width属性设置为100%的技术,图像可以在包含它们的列变得比图像原始尺寸窄的时候,缩放得更小,但总不会变得更大。
  • 第三个关键的组件是媒体查询,可以只使用 CSS 实现,和所有尺寸的屏幕都使用一种布局不同的是,布局是可以改变的:侧栏可以在小屏幕上重新布局,而替代用的导航栏也可以显示出来。

媒介查询

CSS 媒体查询(media query)是响应式设计的关键组成部分,你可以根据各种设备特征和参数是否存在以及对应的值来应用 CSS 样式。

1
2
3
@media media-type and (media-feature-rule) {
/* CSS rules go here */
}

它由以下部分组成:

  • 一个媒体类型,告诉浏览器这段代码是用在什么类型的媒体上的(例如印刷品或者屏幕)。可以指定的媒体类型为:

    • all
    • print
    • screen
    • speech
  • 一个媒体表达式,是一个被包含的 CSS 生效所需的规则或者测试。

  • 一组 CSS 规则,会在测试通过且媒体类型正确的时候应用。

媒体特征规则

在指定了类型以后,你可以用一条规则指向一种媒体特征。

宽和高:

使用最小值和最大值对响应式设计有很多的用处,所以你会很少见到width或height 单独使用的情况。

1
2
3
4
5
@media screen and (min-width: 800px) {
.container {
margin: 1em 2em;
}
}

朝向:

一个受到良好支持的媒体特征是orientation,我们可以用它测得竖放(portrait mode)和横放(landscape mode)模式。要在设备处于横向的时候改变 body 文本颜色的话,可使用下面的媒体查询。对朝向的测试可以帮你建立一个为放设备优化的布局。

1
2
3
4
5
@media (orientation: landscape) {
body {
color: rebeccapurple;
}
}

使用指点设备:

作为四级规范的一部分,hover媒体特征被引入了进来。这种特征意味着你可以测试用户是否能在一个元素上悬浮,这也基本就是说他们正在使用某种指点设备,因为触摸屏和键盘导航是没法实现悬浮的。

1
2
3
4
5
@media (hover: hover) {
body {
color: rebeccapurple;
}
}

如果我们知道用户不能悬浮的话,我们可以默认显示一些交互功能。对于能够悬浮的用户,我们可以选择在悬浮在链接上的时候,让这些功能可用。

还是在四级规范中,出现了pointer媒体特征。它可取三个值:none、fine和coarse。fine指针是类似于鼠标或者触控板的东西,它让用户可以精确指向一片小区域。coarse指针是你在触摸屏上的手指。none值意味着,用户没有指点设备,也许是他们正只使用键盘导航,或者是语音命令。
使用pointer可以在用户使用屏幕时进行交互时,帮你更好地设计响应这种交互的界面。例如,如果你知道用户正在用触摸屏设备交互的时候,你可以建立更大的响应区域。

更复杂的媒体查询

有了所有不同的可用的媒体查询,你可能想要把它们混合起来,或者建立查询列表——其中的任何一个都可以匹配生效。

媒体查询中的“与”逻辑:

为了混合媒体特征,你可以以与在上面使用and很相同的方式,用and来混合媒体类型和特征。

例如,我们可能会想要测得min-width和orientation,而 body 的文字只会在视口至少为 400 像素宽,且设备横放时变为蓝色。

1
2
3
4
5
@media screen and (min-width: 400px) and (orientation: landscape) {
body {
color: blue;
}
}

媒体查询中的“或”逻辑:

如果你有一组查询,且要其中的任何一个都可以匹配的话,那么你可以使用逗号分开这些查询。

在下面的示例中,文本会在视口至少为 400 像素宽的时候或者设备处于横放状态的时候变为蓝色。如果其中的任何一项成立,那么查询就匹配上了。

1
2
3
4
5
@media screen and (min-width: 400px), screen and (orientation: landscape) {
body {
color: blue;
}
}

媒体查询中的“非”逻辑:

你可以用not操作符让整个媒体查询失效。这就直接反转了整个媒体查询的含义。因而在下面的例子中,文本只会在朝向为竖着的时候变成蓝色。

1
2
3
4
5
@media not all and (orientation: landscape) {
body {
color: blue;
}
}

怎么选择断点

可以在一张样式表上加入多条媒体查询,调整整个页面或者部分页面以达到适应各式屏幕尺寸的最佳效果。引入媒体查询,以及样式改变时的点,被叫做*断点(breakpoints)。

开发者工具中的响应式设计模式能很好地帮助弄清楚断点应该设置在哪里。你能容易就能让视口变大和变小,然后看下可以在哪里加入媒体查询、调整设计,从而改善内容。

移动优先的响应式设计

使用媒体查询时的一种通用方式是,为窄屏设备(例如移动设备)创建一个简单的单栏布局,然后检查是否是大些的屏幕,在你知道你有足够容纳的屏幕宽度的时候,开始采用一种多栏的布局。这经常被描述为移动优先设计。

真的需要媒体查询吗?

弹性盒、网格和多栏布局都给了你建立可伸缩的甚至是响应式组件的方式,而不需要媒体查询。

灵活网格

使用灵活网格,你只需要加进去一个断点,在内容看起来不齐整的时候改变设计。例如如果一行随着屏幕大小增加而增长得不可读的长,或者是一个盒子在变窄时把每行的两个单词挤到一起。

早年间进行响应式设计的时候,我们唯一的实现布局的选项是使用float。灵活浮动布局是这样实现的,让每个元素都有一个作为宽度的百分数,而且确保整个布局的和不会超过 100%。在他对于液态网格文章的原文中,Marcotte 详细描述了一种布局的法则,通过使用像素并把布局转化为百分数的方式设计。

target / context = result

例如如果我们的预期栏尺寸为 60 像素,而且它所在的上下文(或者容器)为 960 像素,我们在将零点二的空间移动到右边以后,用 960 去除 60,得到我们能够使用在我们的 CSS 上的值。

1
2
3
.col {
width: 6.25%; /* 60 / 960 = 0.0625 */
}

现代布局技术

现代布局方式,例如多栏布局、伸缩盒和网格默认是响应式的。

多个列

1
2
3
4
5
6
.container {
column-count: 3;
}
.container {
column-width: 10em;
}

伸缩盒

1
2
3
4
5
6
7
.container {
display: flex;
}

.item {
flex: 1;
}

CSS 网格

1
2
3
4
.container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}

响应式图像

响应式图像,使用了<picture>元素和<img> srcset和sizes 特性,可以提供附带着“提示”(描述图像最适合的屏幕尺寸和分辨率的元数据)的多种尺寸,浏览器将会选择对设备最合适的图像,以确保用户下载尺寸适合他们使用的设备的图像。也可以给用于不同尺寸的图像做“艺术指导”,为不同的屏幕尺寸提供不同的图像裁切或者完全不同的图像。

响应式排版

根据屏幕真实使用范围的多少,在媒体查询的同时改变字体大小。

在本例子中,想讲一级标题设置为4rem,也就是说它将会有基础字体的四倍大。但是只想在大些的屏幕上有这么个超大的标题,那先弄个小点的标题,再使用媒体查询,在知道用户使用至少1200px的屏幕的时候,拿大些的尺寸覆写它。

1
2
3
4
5
6
7
8
9
10
11
12
13
html {
font-size: 1em;
}

h1 {
font-size: 2rem;
}

@media (min-width: 1200px) {
h1 {
font-size: 4rem;
}
}

使用视口单位实现响应式排版

一个有趣的方式是使用视口单位vw来实现响应式排版。1vw等同于视口宽度的百分之一,即如果你用vw来设定字体大小的话,字体的大小将总是随视口的大小进行改变。

1
2
3
h1 {
font-size: 6vw;
}

问题在于,当做上面的事情的时候,因为文本总是随着视口的大小改变大小,用户失去了放缩任何使用vw单位的文本的能力。所以你永远都不要只用 viewport 单位设定文本。

这里有一个解决方法,它使用了calc(),如果你将vw单位加到了使用固定大小(例如em或者rem)的值组,那么文本仍然是可放缩的。基本来说,是vw加在了放缩后的值上。

1
2
3
h1 {
font-size: calc(1.5rem + 3vw);
}

这就是说,只需要指定标题的字体大小一次,而不是为移动端设定它,然后再在媒介查询中重新定义它。字体会在你增加视口大小的时候逐渐增大。

视口元标签

看一张响应式页面的 HTML 源代码,通常将会在文档的<head>看到下面的<meta>标签。

1
<meta name="viewport" content="width=device-width,initial-scale=1" />

这个元标签告诉移动端浏览器,它们应该将视口宽度设定为设备的宽度,将文档放大到其预期大小的 100%,在移动端以你所希望的为移动优化的大小展示文档。

如果你有个窄屏布局,在 480 像素及以下的视口宽度下生效,但是视口是按 960 像素设定的,你将不会在移动端看到你的窄屏布局。通过设定width=device-width,你用设备的实际宽度覆写了苹果默认的width=960px,然后你的媒介查询就会像预期那样生效。

所以你应该在你的文档头部总是包含上面那行 <meta>

和视口元标签一起,你可以使用另外几个设定,但大体说来,上面那行就是你想要使用的。

  • initial-scale:设定了页面的初始缩放,我们设定为 1。
  • height:特别为视口设定一个高度。
  • minimum-scale:设定最小缩放级别。
  • maximum-scale:设定最大缩放级别。
  • user-scalable:如果设为no的话阻止缩放。

你应该避免使用minimum-scale、maximum-scale,尤其是将user-scalable设为no。用户应该有权力尽可能大或小地进行缩放,阻止这种做法会引起访问性问题。

支持旧浏览器

需要查明使用旧浏览器浏览你的站点的用户的数量。

你应该同样考虑设备的类型和人们使用你的网站的方式。

无障碍和使用辅助性技术的人总应当被考虑,对于一些站点,这问题可能更加重要。

在 CSS 中构建回滚

CSS 规范包含了在一个物件上同时应用两种布局的时候,解释浏览器如何反应的信息。例如,规范定义如果一个浮动元素同时又是用 CSS 网格布局实现的网格元素(Grid Item)的时候会发生什么。结合浏览器会忽略掉它不会理解的 CSS 的特点组合起来,你就得到了使用我们已经提到的传统技术构建简单布局的方法,在可以理解你的网格布局的现代浏览器中,传统布局方法会被网格布局覆写掉。

如果浏览器器支持网格布局,它会显示网格视图否则它会忽略****display: grid相关的属性,使用浮动布局

回滚方式

浮动和清除

  • 如果浮动和清除的物件变成了弹性或网格物件,浮动和清除属性不再影响布局。

display: inline-block

  • 这种方式能被用来建立列布局,如果一个物件被设为display: inline-block,但是之后变成了弹性或者网格物件,inline-block 行为将会被忽略。

display: table

  • 被设为 CSS 表格布局的物件将会在它们变为弹性或者网格物件的时候不再表现出这种行为。重要的是,被建立起来用于修复表格结构的匿名盒子没有被建立起来。

Multiple-column Layout

  • 对于某些布局,你能用multi-col作为回滚。如果你的容器有任何一个column-*属性被定义,之后变成了网格容器,那么多列行为不会实现。

作为网格的回滚的弹性盒

  • 弹性盒由于受到了 IE10 和 IE11 的支持,比网格有着更好的浏览器支持。不过,如果你把弹性容器做成了网格容器,任何应用到子元素的flex属性都会被忽略。

在浮动布局中,百分数是依照容器计算的——33.333% 是容器宽度的三分之一。但在网格中,这 33.333% 是根据物件所在的网格区域计算的,所以只要网格布局引入进来,物件的大小实际上变成了我们想要的大小的三分之一。

为了处理这种问题,我们需要有能够探测网格是否受到支持的方法,也就是探测它是否会覆写宽度的方法。CSS 在这里为我们提供了一个解决方法。

特性查询

特性查询允许你测试一个浏览器是否支持任何特定的一个 CSS 特性。这就是说,你可以写一些面向不支持某项特性的浏览器的 CSS,然后检查以了解浏览器是否支持,如果支持的话就可以加进你的复杂布局了。

示例中加入了一条特征查询,要是我们知道网格受到支持的话,我们可以用它把我们的物件宽度设定回auto。

1
2
3
4
5
6
7
8
9
10
11
12
13
.item {
float: left;
border-radius: 5px;
background-color: rgb(207, 232, 220);
padding: 1em;
width: 33.333%;
}

@supports (display: grid) {
.item {
width: auto;
}
}

对特性查询的支持,在各现代浏览器中都是很好的。

先在任何特性查询以外,编写我们的旧 CSS。不支持网格也不支持特性查询的浏览器会使用这部分它们可以理解的布局信息,将其他东西全都忽略掉。支持特性查询和 CSS 网格等的浏览器会运行网格代码和特性查询之内的代码。

弹性盒的旧版本

在旧版浏览器中,你可以找到弹性盒规范的旧有修订版本。在编写这篇文章的时候,这大多数是 IE10 的问题,它在弹性盒上使用了-ms-前缀。这也意味着现在还存在着一些过时的文章和教程,这篇有用的指导帮助你分辨你看到的信息;如果你需要在很旧的浏览器中需要 flex 支持的话,它也会帮到你。

IE10 和 IE11 的带前缀版的网格

CSS 网格规范最初成形于 IE10,也就是说尽管 IE10 和 IE11 不支持现代的网格,虽然这种网格和本站记载的现代布局不同,它们还是有一个很堪用的网格布局版本。IE10 和 IE11 的实现是以-ms-为前缀的,也就是说你可以给这两个浏览器用,而在非微软浏览器上,这种属性会被忽略。不过 Edge 仍然能理解旧语法,所以小心点,让每个东西都安全地在你的现代网格 CSS 中覆写。

测试旧浏览器

由于大多数浏览器都支持弹性盒和网格,测试旧浏览器想想就很难。一种方式是使用在线的测试工具,例如 Sauce Labs,在跨浏览器测试模块里有详细说明。

你也可以下载安装虚拟机,在你的电脑的容器环境中运行旧版浏览器。能够使用旧版 IE 是很有用的,为此,微软已经制作了一些可以免费下载的虚拟机。这些对 Mac、Windows 和 Linux 操作系统都是可以用的,所以即使你没有 Windows 电脑,这也是一个测试旧的和现代 Windows 浏览器的绝佳办法。

作者

冷冷

发布于

2021-10-09

更新于

2021-11-10

许可协议

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×