传统布局方法
网格系统是 CSS 布局中非常常见的特性,在 CSS 网格布局出现之前,它们通常使用浮动或其他布局特性实现。你将你的布局想象成一组固定数量的列(例如 4、6 或 12),然后将你的内容列放入这些虚构的列中。在本文中,我们将探讨这些旧方法是如何工作的,以便你能理解如果你在较旧的项目中工作,它们是如何被使用的。
CSS 网格布局之前的布局和网格系统
对于任何来自设计背景的人来说,在最近之前 CSS 没有内置的网格系统,我们似乎一直在使用各种次优的方法来创建类似网格的设计,这可能令人惊讶。我们现在称这些为“遗留”方法。
对于新项目,在大多数情况下,CSS 网格布局将与一种或多种其他现代布局方法结合使用,作为任何布局的基础。但是,你偶尔会遇到使用这些遗留方法的“网格系统”。了解它们的工作原理以及它们与 CSS 网格布局的不同之处是值得的。
本课将解释基于浮动和 flexbox 的网格系统和网格框架是如何工作的。学习了网格布局后,你可能会惊讶于这一切看起来多么复杂!如果你需要为不支持较新方法的浏览器创建回退代码,以及让你能够处理使用这些类型系统的现有项目,这些知识将对你有帮助。
在我们探索这些系统时,值得记住的是,它们都没有真正像 CSS 网格布局那样创建网格。它们通过给项目一个大小,并将它们推来推去,使它们排列成看起来像网格的方式工作。
两列布局
让我们从最简单的例子开始——一个两列布局。你可以通过在你的计算机上创建一个新的 index.html
文件,用 简单的 HTML 模板填充它,并将下面的代码插入到适当的地方来进行操作。在该部分的底部,你可以看到最终代码的样子。
首先,我们需要一些内容放到我们的列中。用以下内容替换当前在 body 中的内容
<h1>2 column layout example</h1>
<div>
<h2>First column</h2>
<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>
</div>
<div>
<h2>Second column</h2>
<p>
Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada
ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed
est. Nam id risus quis ante semper consectetur eget aliquam lorem. Vivamus
tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies
lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis
vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque
penatibus et magnis dis parturient montes, nascetur ridiculus mus.
</p>
</div>
每一列都需要一个外部元素来包含它的内容,让我们可以一次性操作所有内容。在这个例子中,我们选择了 <div>
,但你可以选择更具语义性的内容,比如 <article>
、<section>
和 <aside>
,或者其他任何东西。
现在是 CSS。首先,将以下内容应用于你的 HTML,以提供一些基本设置
body {
width: 90%;
max-width: 900px;
margin: 0 auto;
}
body 的宽度将是视窗的 90%,直到它达到 900 像素宽,在这种情况下,它将保持固定在这个宽度并自身居中在视窗中。默认情况下,它的子元素(h1 和两个 <div>
)将跨越 body 的 100% 宽度。如果我们想要两个 <div>
彼此并排浮动,我们需要将它们的宽度设置为其父元素的 100% 或更小,这样它们才能彼此并排放置。将以下内容添加到你的 CSS 的底部
div:nth-of-type(1) {
width: 48%;
}
div:nth-of-type(2) {
width: 48%;
}
在这里,我们设置了两个元素的宽度都是其父元素的 48%——这总共是 96%,留下 4% 可以作为两列之间的间距,使内容有空间呼吸。现在我们只需要使列浮动,就像这样
div:nth-of-type(1) {
width: 48%;
float: left;
}
div:nth-of-type(2) {
width: 48%;
float: right;
}
将这些内容组合在一起应该会得到以下结果
你会注意到,我们在这里对所有宽度都使用了百分比——这是一个很好的策略,因为它创建了一个流式布局,一个可以适应不同屏幕大小并在较小屏幕大小下保持列宽比例相同的布局。尝试调整浏览器窗口的宽度,亲自看看。这是响应式网页设计的宝贵工具。
注意: 你可以在 0_two-column-layout.html 上查看此示例的运行情况(也可以查看 源代码)。
创建简单的传统网格框架
大多数遗留框架使用 float
属性的行为,将一列浮动到另一列的旁边,以创建一个看起来像网格的东西。完成使用浮动创建网格的过程,可以让你了解它是如何工作的,并介绍一些更高级的概念,在这些概念的基础上,你可以学习关于 浮动和清除 的课程中的内容。
最容易创建的网格框架类型是固定宽度框架——我们只需要确定我们想要设计总共占用的宽度、我们想要多少列以及列和间距的宽度。如果我们决定将设计放在一个网格上,其中列根据浏览器宽度增长和缩小,那么我们需要计算列和它们之间间距的百分比宽度。
在接下来的几节中,我们将探讨如何创建这两种方法。我们将创建一个 12 列网格——一个非常常见的选择,它被认为非常适合不同的情况,因为 12 可以被 6、4、3 和 2 整除。
简单的固定宽度网格
让我们首先创建一个使用固定宽度列的网格系统。
首先,从我们的示例 simple-grid.html 文件中创建一个本地副本,该文件在其 body 中包含以下标记。
<div class="wrapper">
<div class="row">
<div class="col">1</div>
<div class="col">2</div>
<div class="col">3</div>
<div class="col">4</div>
<div class="col">5</div>
<div class="col">6</div>
<div class="col">7</div>
<div class="col">8</div>
<div class="col">9</div>
<div class="col">10</div>
<div class="col">11</div>
<div class="col">12</div>
</div>
<div class="row">
<div class="col span1">13</div>
<div class="col span6">14</div>
<div class="col span3">15</div>
<div class="col span2">16</div>
</div>
</div>
我们的目标是将它变成一个演示网格,在 12 列网格上包含两行——上面一行演示单个列的大小,下面一行演示网格上一些不同大小的区域。
在 <style>
元素中,添加以下代码,它将包装器容器的宽度设置为 980 像素,在右侧填充 20 像素。这给我们留下了 960 像素用于我们的总列/间距宽度——在这种情况下,填充是从总内容宽度中减去的,因为我们已将 box-sizing
设置为所有元素的 border-box
(有关更多说明,请参阅 替代 CSS 盒子模型)。
* {
box-sizing: border-box;
}
body {
width: 980px;
margin: 0 auto;
}
.wrapper {
padding-right: 20px;
}
现在,使用包装在网格每一行周围的行容器来清除一行与另一行的关系。在之前的规则下方添加以下规则
.row {
clear: both;
}
应用此清除意味着我们不需要用填充整个 12 列的元素来完全填充每一行。行将保持分离,并且不会相互干扰。
列之间的间距为 20 像素宽。我们将这些间距作为每一列左侧的边距创建——包括第一列,以平衡容器右侧的 20 像素填充。因此,我们总共有 12 个间距——12 x 20 = 240。
我们需要从我们的总宽度 960 像素中减去它,这样我们的列就只剩下 720 像素。如果我们现在将其除以 12,我们就知道每一列应该有 60 像素宽。
我们的下一步是为 .col
类创建一个规则,使其向左浮动,并赋予它 margin-left
20 像素,以形成间距,并赋予它 width
60 像素。将以下规则添加到你的 CSS 的底部
.col {
float: left;
margin-left: 20px;
width: 60px;
background: rgb(255 150 150);
}
现在,上面一行单个列将整齐地排列成网格。
注意: 我们还给每一列赋予了一个浅红色的颜色,以便你可以确切地看到每一个占用了多少空间。
我们想要跨越多列的布局容器需要被赋予特殊的类,以调整它们的 width
值,使其达到所需的列数(加上它们之间的间距)。我们需要创建一个额外的类来允许容器跨越 2 到 12 列。每个宽度都是将该数量的列的列宽度加上间距宽度相加得出的结果,而间距宽度总是比列数少一个。
将以下内容添加到你的 CSS 的底部
/* Two column widths (120px) plus one gutter width (20px) */
.col.span2 {
width: 140px;
}
/* Three column widths (180px) plus two gutter widths (40px) */
.col.span3 {
width: 220px;
}
/* And so on… */
.col.span4 {
width: 300px;
}
.col.span5 {
width: 380px;
}
.col.span6 {
width: 460px;
}
.col.span7 {
width: 540px;
}
.col.span8 {
width: 620px;
}
.col.span9 {
width: 700px;
}
.col.span10 {
width: 780px;
}
.col.span11 {
width: 860px;
}
.col.span12 {
width: 940px;
}
创建了这些类之后,我们现在就可以在网格上排列不同宽度的列了。尝试保存并加载页面,看看效果。
尝试修改元素上的类,甚至添加和删除一些容器,看看你可以如何改变布局。例如,你可以使第二行看起来像这样
<div class="row">
<div class="col span8">13</div>
<div class="col span4">14</div>
</div>
现在你已经创建了一个网格系统,你可以定义每一行的行和列数,然后用你需要的内容填充每个容器。太棒了!
创建流体网格
我们的网格工作得很好,但是它有一个固定宽度。我们真正想要的是一个灵活的(流体)网格,它会随着浏览器 视窗 中可用空间的变化而增长和缩小。为了实现这一点,我们可以将参考像素宽度改为百分比。
将固定宽度转换为灵活的基于百分比的宽度的公式如下。
target / context = result
对于我们的列宽度,我们的目标宽度是 60 像素,而我们的上下文是 960 像素的包装器。我们可以使用以下公式来计算百分比。
60 / 960 = 0.0625
然后我们将小数点向右移动两位,得到一个 6.25% 的百分比。因此,在我们的 CSS 中,我们可以用 6.25% 替换 60 像素的列宽度。
我们需要对我们的间距宽度做同样的事情
20 / 960 = 0.02083333333
因此,我们需要用 2.08333333% 替换我们的 .col
规则上的 20 像素 margin-left
和 .wrapper
上的 20 像素 padding-right
。
更新我们的网格
要开始本节的操作,请从之前的示例页面创建一个新副本,或者从我们的 simple-grid-finished.html 代码中创建一个本地副本,作为起点。
将第二个 CSS 规则(带有 .wrapper
选择器)更新如下
body {
width: 90%;
max-width: 980px;
margin: 0 auto;
}
.wrapper {
padding-right: 2.08333333%;
}
我们不仅赋予了它一个百分比 width
,还添加了一个 max-width
属性,以防止布局变得太宽。
接下来,将第四个 CSS 规则(带有 .col
选择器)更新如下
.col {
float: left;
margin-left: 2.08333333%;
width: 6.25%;
background: rgb(255 150 150);
}
现在是比较繁琐的部分——我们需要更新所有 .col.span
规则,使用百分比而不是像素宽度。这需要使用计算器,花费一些时间;为了节省你的时间,我们已经帮你完成了以下操作。
用以下内容更新 CSS 规则的底部块
/* Two column widths (12.5%) plus one gutter width (2.08333333%) */
.col.span2 {
width: 14.58333333%;
}
/* Three column widths (18.75%) plus two gutter widths (4.1666666) */
.col.span3 {
width: 22.91666666%;
}
/* And so on… */
.col.span4 {
width: 31.24999999%;
}
.col.span5 {
width: 39.58333332%;
}
.col.span6 {
width: 47.91666665%;
}
.col.span7 {
width: 56.24999998%;
}
.col.span8 {
width: 64.58333331%;
}
.col.span9 {
width: 72.91666664%;
}
.col.span10 {
width: 81.24999997%;
}
.col.span11 {
width: 89.5833333%;
}
.col.span12 {
width: 97.91666663%;
}
现在保存你的代码,在浏览器中加载它,并尝试更改视窗宽度——你应该会看到列宽度很好地调整以适应它。
使用 calc() 函数进行更简单的计算
您可以使用 calc()
函数在 CSS 中直接进行数学运算——这允许您在 CSS 值中插入简单的数学表达式,以计算某个值应该是什么。它在需要进行复杂数学运算时特别有用,您甚至可以计算使用不同单位的运算,例如“我希望这个元素的高度始终是其父元素高度的 100%,减去 50 像素”。请参阅 MediaStream Recording API 教程中的这个示例。
无论如何,让我们回到我们的网格!任何跨越我们网格中多个列的列,其总宽度都等于 6.25% 乘以跨越的列数,再加上 2.08333333% 乘以间距的数量(始终等于列数减 1)。calc()
函数允许我们在宽度值中直接进行此计算,因此对于任何跨越 4 列的项目,我们可以执行以下操作,例如
.col.span4 {
width: calc((6.25% * 4) + (2.08333333% * 3));
}
尝试用以下内容替换您的底部规则块,然后在浏览器中重新加载它,看看您是否得到相同的结果
.col.span2 {
width: calc((6.25% * 2) + 2.08333333%);
}
.col.span3 {
width: calc((6.25% * 3) + (2.08333333% * 2));
}
.col.span4 {
width: calc((6.25% * 4) + (2.08333333% * 3));
}
.col.span5 {
width: calc((6.25% * 5) + (2.08333333% * 4));
}
.col.span6 {
width: calc((6.25% * 6) + (2.08333333% * 5));
}
.col.span7 {
width: calc((6.25% * 7) + (2.08333333% * 6));
}
.col.span8 {
width: calc((6.25% * 8) + (2.08333333% * 7));
}
.col.span9 {
width: calc((6.25% * 9) + (2.08333333% * 8));
}
.col.span10 {
width: calc((6.25% * 10) + (2.08333333% * 9));
}
.col.span11 {
width: calc((6.25% * 11) + (2.08333333% * 10));
}
.col.span12 {
width: calc((6.25% * 12) + (2.08333333% * 11));
}
注意:您可以在 fluid-grid-calc.html 中查看我们的完成版本(也可以 查看实时版本)。
语义与“非语义”网格系统
在您的标记中添加类来定义布局意味着您的内容和标记将与您的视觉呈现绑定在一起。您有时会听到这种 CSS 类的使用被描述为“非语义”——描述内容的外观——而不是描述内容的语义类使用。这就是我们的 span2
、span3
等类的案例。
这不是唯一的方法。您可以改为决定您的网格,然后将大小信息添加到现有语义类的规则中。例如,如果您有一个带有 content
类别的 <div>
,并且您希望它跨越 8 列,您可以从 span8
类中复制宽度,得到如下规则
.content {
width: calc((6.25% * 8) + (2.08333333% * 7));
}
注意:如果您要使用诸如 Sass 这样的预处理器,您可以创建一个简单的 mixin 为您插入该值。
在我们的网格中启用偏移容器
我们创建的网格工作良好,只要我们希望所有容器都与网格的左侧对齐即可。如果我们希望在第一个容器之前(或容器之间)留出一个空列空间,我们需要创建一个偏移类,为我们的站点添加左边距,以便在视觉上将其推到网格中。更多数学运算!
让我们试试看。
从您之前的代码开始,或使用我们的 fluid-grid.html 文件作为起点。
让我们在 CSS 中创建一个类,该类将容器元素偏移一个列宽。将以下内容添加到您的 CSS 底部
.offset-by-one {
margin-left: calc(6.25% + (2.08333333% * 2));
}
或者,如果您更喜欢自己计算百分比,请使用以下内容
.offset-by-one {
margin-left: 10.41666666%;
}
您现在可以将此类添加到您希望在其左侧留出一个列宽空隙的任何容器中。例如,如果您在 HTML 中有以下内容
<div class="col span6">14</div>
尝试用以下内容替换它
<div class="col span5 offset-by-one">14</div>
注意:请注意,您需要减少跨越的列数,以腾出空间用于偏移!
尝试加载并刷新以查看差异,或查看我们的 fluid-grid-offset.html 示例(也可以 查看实时运行)。完成的示例应该如下所示
注意:作为一项额外的练习,您可以实现 offset-by-two
类吗?
浮动网格限制
当使用这样的系统时,您确实需要确保您的总宽度正确相加,并且您不会在行中包含跨越超出该行可容纳的列数的元素。由于浮动的工作方式,如果网格列数变得太宽,末端的元素将向下移到下一行,从而破坏网格。
还要记住,如果元素的内容比它们所占据的行宽,它将溢出并看起来很糟糕。
这个系统最大的限制是它本质上是一维的。我们正在处理列,以及跨越元素跨列,但不是跨行。使用这些旧的布局方法,很难控制元素的高度而不显式设置高度,这是一种非常不灵活的方法——它只有在您可以保证您的内容具有特定高度的情况下才有效。
弹性盒网格?
如果您阅读了我们之前关于 flexbox 的文章,您可能会认为 flexbox 是创建网格系统的理想解决方案。有许多基于 flexbox 的网格系统可用,并且 flexbox 可以解决我们在上面创建网格时已经发现的许多问题。
但是,flexbox 从未被设计为网格系统,并且在用作网格系统时会带来新的挑战。作为这个的简单示例,我们可以使用与上面相同的示例标记,并使用以下 CSS 来设置 wrapper
、row
和 col
类的样式
body {
width: 90%;
max-width: 980px;
margin: 0 auto;
}
.wrapper {
padding-right: 2.08333333%;
}
.row {
display: flex;
}
.col {
margin-left: 2.08333333%;
margin-bottom: 1em;
width: 6.25%;
flex: 1 1 auto;
background: rgb(255 150 150);
}
您可以尝试在您自己的示例中进行这些替换,或者查看我们的 flexbox-grid.html 示例代码(也可以 查看实时运行)。
在这里,我们将每行都变成了一个 flex 容器。在基于 flexbox 的网格中,我们仍然需要行,以便允许我们拥有总和不到 100% 的元素。我们将该容器设置为 display: flex
。
在 .col
上,我们将 flex
属性的第一个值 (flex-grow
) 设置为 1,以便我们的项目可以增长,第二个值 (flex-shrink
) 设置为 1,以便项目可以缩小,第三个值 (flex-basis
) 设置为 auto
。由于我们的元素设置了 width
,auto
将使用该宽度作为 flex-basis
值。
在顶行,我们在网格上获得了十二个整齐的框,并且当我们更改视口宽度时,它们会等比例地增长和缩小。然而,在下一行,我们只有四个项目,这些项目也从 60px 的基点增长和缩小。由于只有四个项目,它们可以比上面行中的项目增长得多,结果是它们在第二行上都占据相同的宽度。
要解决这个问题,我们仍然需要包含我们的 span
类,以提供一个宽度来替换 flex-basis
用于该元素的值。
它们也不尊重上面项目使用的网格,因为它们不知道任何有关它的事情。
Flexbox 本质上是一维的。它处理单个维度,即行或列。我们无法为列和行创建严格的网格,这意味着如果我们要将 flexbox 用于我们的网格,我们仍然需要像浮动布局一样计算百分比。
在您的项目中,您可能仍然选择使用 flexbox “网格”,因为与浮动相比,flexbox 提供了额外的对齐和空间分配功能。但是,您应该意识到,您仍然在使用一种工具来做它不是为其设计的事情。因此,您可能会觉得它让您为了获得想要的结果而跳过额外的步骤。
第三方网格系统
现在我们已经了解了网格计算背后的数学原理,我们已经可以很好地查看一些常用的第三方网格系统。如果您在网上搜索“CSS 网格框架”,您会发现一大堆可供选择的选项。流行的框架,如 Bootstrap 和 Foundation 包含网格系统。也有一些独立的网格系统,它们要么使用 CSS 开发,要么使用预处理器开发。
让我们看一下其中一个独立的系统,因为它展示了使用网格框架的常见技术。我们将使用的网格是 Skeleton 的一部分,Skeleton 是一个简单的 CSS 框架。
要开始,请访问 Skeleton 网站,然后选择“下载”以下载 ZIP 文件。解压缩此文件并将 skeleton.css 和 normalize.css 文件复制到一个新目录中。
复制我们的 html-skeleton.html 文件,并将其保存在与 skeleton 和 normalize CSS 相同的目录中。
在 HTML 页面中包含 skeleton 和 normalize CSS,方法是在其头部添加以下内容
<link href="normalize.css" rel="stylesheet" />
<link href="skeleton.css" rel="stylesheet" />
Skeleton 包含的不仅仅是一个网格系统——它还包含用于排版和其他页面元素的 CSS,您可以将其用作起点。但是,现在我们将保留这些默认设置——我们真正感兴趣的是网格。
注意:Normalize 是 Nicolas Gallagher 编写的非常有用的 CSS 库,它会自动执行一些有用的基本布局修复,并使默认元素样式在不同浏览器之间更加一致。
我们将使用与我们之前的示例类似的 HTML。将以下内容添加到您的 HTML 主体中
<div class="container">
<div class="row">
<div class="col">1</div>
<div class="col">2</div>
<div class="col">3</div>
<div class="col">4</div>
<div class="col">5</div>
<div class="col">6</div>
<div class="col">7</div>
<div class="col">8</div>
<div class="col">9</div>
<div class="col">10</div>
<div class="col">11</div>
<div class="col">12</div>
</div>
<div class="row">
<div class="col">13</div>
<div class="col">14</div>
<div class="col">15</div>
<div class="col">16</div>
</div>
</div>
要开始使用 Skeleton,我们需要给包装器 <div>
一个名为 container
的类——这已经包含在我们的 HTML 中。这将以 960 像素的最大宽度居中内容。您可以看到框现在永远不会超过 960 像素。
您可以查看 skeleton.css 文件,以查看应用此类时使用的 CSS。<div>
使用 auto
左边距和右边距居中,并在左右两侧应用 20 像素的填充。Skeleton 还像我们之前一样将 box-sizing
属性设置为 border-box
,因此此元素的填充和边框将包含在总宽度中。
.container {
position: relative;
width: 100%;
max-width: 960px;
margin: 0 auto;
padding: 0 20px;
box-sizing: border-box;
}
元素只能在它们位于行内时才能成为网格的一部分,因此与我们之前的示例一样,我们需要一个额外的 <div>
或其他具有 row
类的元素,嵌套在内容 <div>
元素和容器 <div>
之间。我们也已经这样做了。
现在让我们来布局容器框。Skeleton 基于 12 列网格。顶行框都需要 one column
类,以使它们跨越一列。
现在添加这些,如以下代码片段所示
<div class="container">
<div class="row">
<div class="one column">1</div>
<div class="one column">2</div>
<div class="one column">3</div>
/* and so on */
</div>
</div>
接下来,为第二行上的容器添加类,解释它们应该跨越的列数,如下所示
<div class="row">
<div class="one column">13</div>
<div class="six columns">14</div>
<div class="three columns">15</div>
<div class="two columns">16</div>
</div>
尝试保存您的 HTML 文件并在浏览器中加载它,以查看效果。
注意:如果您遇到此示例无法正常运行的问题,请尝试扩大您用来查看它的窗口(如果窗口太窄,网格将不会如这里描述的那样显示)。如果这不起作用,请尝试将其与我们的 html-skeleton-finished.html 文件进行比较(也请查看 实时运行)。
如果您查看 skeleton.css 文件,您可以看到它是如何工作的。例如,Skeleton 定义了以下内容来为添加了“三列”类的元素设置样式。
.three.columns {
width: 22%;
}
所有 Skeleton(或任何其他网格框架)所做的只是设置预定义的类,您可以通过将它们添加到您的标记中来使用它们。这与您自己计算这些百分比完全相同。
如您所见,使用 Skeleton 时,我们只需要编写很少的 CSS 代码。当我们在标记中添加类时,它会处理所有浮动操作。正是这种将布局责任交给其他东西的能力,使使用网格系统框架成为一个令人信服的选择!然而,如今,随着 CSS 网格布局的出现,许多开发人员正在放弃这些框架,转而使用 CSS 提供的内置原生网格。