首先,设计你的删格.
你是要使用等宽的还是不等宽的网格列?要有多少列数?间隔和列的大小是多少?
当你回答了上面的问题你只能做出正确的网格计算。为了解决大家的困扰,我写了一篇设计删格。如果你正想学习设计一个删格系统可以读一下。
其次,你需要明确你的删格系统在不同视口的表现
当屏幕视口发生变化时你要实时重新计算列和间隔么?当间隔保持不变时,你要改变列的大小么?在明确的分界点上你要改变删格列的数量么?
你需要好好回答这些问题。在如何计算列宽和间隔宽上,这些会给你一些线索。在提到的那篇设计删格中我也写了这些要考虑的东西,你不确定这些问题的话可以参考下。
最后,你愿意在HTML里写删格类吗?.
当涉及到删格系统时,前端世界拆分出两个派系。
一派就是在HTML中写入删格类名(以Bootstrap和Foundation为代表)。我称这一派HTML删格系统。HTML像下面这样:
<div class="container">
<div class="row">
<div class="col-md-9">Content</div>
<div class="col-md-3">Sidebar</div>
</div>
</div>
第二个派系主张在CSS中创建删格。我称之为CSS 删格系统。
CSS删格系统的HTML代码相比HTML删格系统的要简单一点。对于同样的页面你需要创建的标签也会少一点。同时,你也不需要记住这些删格类名是什么:
<div class="content-sidebar">
<div class="content"></div>
<div class="sidebar"></div>
</div>
另一方面,CSS删格系统中的CSS更复杂。你需要仔细想清楚才能取得一个简单的解决方案(如果你从没创建过)。
我会选择什么?
许多前端大牛会选择CSS删格系统。我,也不例外,属于CSS删格派系(当然我不敢称自己是大牛)。
关于我为什么选择CSS删格系统而不是HTML删格系统,我写过另一篇文章,如果你感兴趣可以看下。我还写了从HTML删格到CSS删格,如果你有兴趣做个转换,这篇可以帮你从HTML删格系统迁移到CSS删格系统。 (这么多文章… 😢)
总之,这就是在建立删格系统之前你需要明确的三件事。概括下就是:
-
系统设计
-
在不同视口的删格表现
-
是否使用HTML或者CSS删格系统
如果我们有了这些必要知识我们只需要继续前进就行了。这篇文章的剩下部分就是我们接下来要做的事情:
-
这个删格系统有一个1140px的最大宽度,具备12个75px宽的列以及20px的间隔。(参考设计删格里如何获得这些数值的提示)
-
当视口大小变了,在间隔保留20px固定大小的同时应该实时重新计算列宽大小。(在设计删格中我提到了为什么会选择这种展现形式)
-
我打算使用CSS删格系统(在这篇文章中有我为什么建议用CSS删格的原因)
有了这些,让我们开始吧!
建立删格系统
建立删格系统有八个步骤,概括如下:
-
选择一个标准来创建删格宽度
-
设置
box-sizing
为border-box
-
创建一个删格容器
-
计算列宽
-
决定间隔定位
-
创建一个调试网格
-
让布局变化
-
让你的布局响应变化
一旦你想明白了,这八个步骤大部分是相对明确简单的。我会展开每个步骤详细说明其中你需要知道每个事项。
Step 1: 选择一个标准
使用CSS删格,FlexBox,或者简单原始的floats来创建删格?对每个标准来说你的考虑事项以及实现细节都会有所不同。
CSS删格是到目前为止三种标准中创建网格最好的工具(因为是网格嘛 😎)。遗憾的是,现在支持CSS删格会留下更多需要被解决的问题(原文:support for CSS grid leaves more to be desired right now)。每个浏览器将CSS删格布局隐藏在一个标志后面,这就是为什么我们不会在这篇文章中去碰它。如果你对CSS删格有兴趣,我强烈建议参考Rachel Andrew的作品。
接下来,我们来看看Flexbox和Floats。使用这两个标准的注意事项是相似的,所以你可以挑一个继续跟着这篇文章走下去。这里我将使用Floats,因为它对初学者来说更容易解释理解。
但是如果你选择用Flexbox,请记住这里有一些细微的差别你需要调整。
Step 2: 设置box-sizing
为border-box
box-sizing
属性可以改变浏览器用来计算width
和height
属性的默认CSS盒模型。通过改变 box-sizing
为border-box
,我们可以更轻松地计算列和间隔的大小(你接下来就会明白为什么)。
下面这张图,总结了width
在不同的box-sizing
值下是如何计算的。
box-sizing属性及其如何影响宽度计算
在一个网站上,我通常将所有元素的box-sizing
值都设为 border-box
,这样width
和 height
计算全部都保持了一致(还有直观)。就这样:
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
提示:如果你需要一个更深入理解box-sizing
,我建议你看 这个.
Step 3: 创建一个删格容器
每个栅格有一个决定了它最大宽度的容器。我把它取名为.l-wrap
。这个.l-
前缀表示layout, 这个是我自从读了Jonathan Snook的SMACSS之后一直使用的命名约定。
.l-wrap {
max-width: 1140px;
margin-right: auto;
margin-left: auto;
}
注意:出于可访问性和可响应的目的,我强烈建议使用相对单位比如em
或者rem
而不是像素。但这篇文章中,我都使用了像素,因为这比较容易理解。
Step 4: 计算列宽
记住,我们使用floats来创建列和间隔。用它的时候,我们只有五个属性可以用(如果用flexbox的话会多一些);这五个是:
-
width
-
margin-right
-
margin-left
-
padding-right
-
padding-left
如果你还记得,CSS栅格系统的HTML是类似这样的:
<div class="l-wrap">
<div class="three-col-grid">
<div class="grid-item">Grid item</div>
<div class="grid-item">Grid item</div>
<div class="grid-item">Grid item</div>
</div>
</div>
从这段HTML中我们了解到这个网格有一行三列,也没有额外的创建间隔的元素,这就是说:
-
创建列需要
width
属性 -
创建间隔用
margin
或者padding
属性
如果我们同时思考列和间隔,问题就会变得复杂。所以让我们假设我们先创建一个没有间隔的网格。
这样一个网格的输出就会像这样:
没有间隔的三列网格
这里就是我们需要进行一些计算的地方了。我们知道网格有一个1140px的最大宽度,那么每一列的就是380px(1140 ÷ 3
)。
.three-col-grid .grid-item {
width: 380px;
float: left;
}
到目前为止,一切都很顺利。我们创建了一个在大于1140px的屏幕视口上可以良好展示的网格。但不好的是,当视口小于1140px时就不能正常展示了。
小于1140px时网格就成这样了
这就意味着对于网格列我们不能使用像素作为单位。我们需要一个单位可以随视口变化而变化。
这就是说我们不能使用像素作为我们的度量方法。我们需要一个可以根据容器宽度重新计算的单位。唯一可以实现这种方式的单位就是百分比(%
)。所以,我们用百分比的方式定义宽度:
.three-col-grid .grid-item {
width: 33.33333%;
float: left;
}
上面的代码可以得到一个没有任何间隔的简单三列网格。当浏览器窗口大小改变时,这三列会按比例改变。
没有间隔的三列网格
在我们继续之前还有一件事。每当所有的子元素在容器中浮动时,容器的高度就会塌陷。这个现象称为 浮动塌陷。就好像这个容器里不包含任何子元素。
浮动塌陷(图片来自CSS Tricks)
为了解决这个问题,我们需要一个clearfix,像这样:
.three-col-grid:after {
display: table;
clear: both;
content: '';
}
如果你使用像Sass那样的预处理,你可以转换到一个mixin里,这样能让你在不同的地方方便引入同样的代码。
// Clearfix
@mixin clearfix {
&:after {
display: table;
clear: both;
content: '';
}
}
// Usage
.three-col-grid { @include clearfix; }
只要完成了列数的部分,接下来就是建立一些间隔了。
Step 5: 决定间隔位置
目前看来,我们知道应该用margin
或padding
来设计间隔。但是我们应该用哪一个呢?
如果你概览一下,就会快速注意到你有四种可能的方式来设计这些间隔。
-
间隔可以通过margins放置在一边
-
间隔可以通过paddings放置在一边
-
间隔可以通过margins被相等的分隔在两边
-
间隔可以通过paddings被相等的分隔在两边
创建列和间隔的4种方式
从这里就开始变得复杂了。你需要用不同的方式计算列的宽度,这取决于你用的方法。
我们将会一个一个的讲这些方法,看看它们的不同之处。慢慢来体会吧。
开始咯:
Method 1: 单边间隔(Margin)
用这种方法,是通过margin
属性来创建间隔。这个间隔是在列的左边还是右边,这取决于你选择哪边。
为了让初学者明白,假设你选择将间隔放在右边,那么你将要做的是:
.grid-item {
/* Need to recalculate width property */;
margin-right: 20px;
float: left;
}
根据这张图你要重新计算列宽:
使用margins实现的单边间隔
从这张图上可以看到1140px等于三列和两个间隔。
这里有个问题...我们需要用百分比表示列宽,但间隔固定是20px,我们没法同时用两个不同的单位进行计算。
好吧,这在以前是不太可能,但今非昔比了。
你可以使用CSS的calc
方法将其他单位和百分比混合使用。它会重新得到百分比的单位值来快速执行计算。
这样一来你可以将width通过一个函数得到,然后浏览器就会自动为你计算这个值:
.grid-item {
width: calc((100% - 20px * 2) / 3);
/* other properties */
}
很棒吧!
得到列宽后,你需要删除来自最右边网格列的最后一个间隔。你可以这样写:
.grid-item:last-child {
margin-right: 0;
}
多数时候,当你去掉最右边项的最后一个间隔时,你也会想将它浮动在右边防止子像素舍入错误将最后一列放到了下一行导致把网格搞的一团糟。这只是会发生在子像素向上舍入的浏览器中。
子像素舍入错误可能会将最后一项放到下一行造成网格混乱。
.grid-item:last-child {
margin-right: 0;
float: right;
}
唉,终于讲到这里了。还有件事。
到现在为止,代码还算不错,如果你的网格只包含一行。但是,如果有多行,这些代码可不会剪切网格。
如果有多行,我们的代码就行不通了。 我们需要做的是去掉每一行的最右边项的右边距。实现这种最好的方式是用nth-child()
:
/* For a 3-column grid */
.grid-item:nth-child(3n+3) {
margin-right: 0;
float: right;
}
这就是用margins实现单边间隔你所需要的所有代码。在codepen上你可以看看。
查看 CodePen上用margins实现的单边间隔网格 by Zell Liew (@zellwk).
注意:Calc方法在IE8和Opera mini上不起作用。如果你需要支持这两种浏览器的话,可能得考虑其他方法。
Method 2: 单边间隔 (Padding)
和用margins实现的单边间隔类似,这种方式也是要求你将间隔放在列的一边。假设还是选择的右边。
.grid-item {
/* width property */
padding-right: 20px;
float: left;
}
然后,你可以通过这张图重新计算列宽。
用padding实现的单边间隔
注意到宽度和上一种方式的不同了么?因为我们把box-sizing
属性转换成border-box
了。所以现在width
的计算包括了padding
值。
在这种情况下,三列中前两列比最后一个要宽,这样最终会导致奇怪的计算结果以及难以理解的CSS代码。
我建议千万不要尝试这种方式。(如果你继续这样,代码会变得非常丑陋。请在你风险控制范围内尝试!)
Method 3: 拆分间隔 (Margin)
用这种方式,你得把间隔分成2个并将它们分别置于在列的两边。代码像这样:
.grid-item {
/* Width property */
margin-right: 10px;
margin-left: 10px;
float: left;
}
然后,根据下面这张图计算列宽:
用margin拆分间隔
根据之前我们了解到的,你需要用calc()
方法来计算列宽。对于现在这种情况,你要从100%的宽度里去掉三个间隔,再除以3就得到列宽了。换言之,列宽等于calc((100% - 20px * 3) / 3)
。
.grid-item {
width: calc((100% - 20px * 3) / 3);
margin-right: 10px;
margin-left: 10px;
float: left;
}
就这样!(对于多行网格其实没有其他额外需要做的 😉)。下面是代码展示: 查看 用margins实现网格间隔拆分 by Zell Liew (@zellwk).
Method 4: 拆分间隔 (Padding)
这个和上一个类似。也是拆分间隔并分别放在列两边。但这次,是使用padding来替换间隔。
.grid-item {
/* width property */
padding-right: 10px;
padding-left: 10px;
float: left;
}
然后,再像下面这样计算列宽:
通过padding拆分间隔
注意到这次计算列宽是不是简单多了?这就对啦,每一列分界点刚好将网格分成三等份。
.grid-item {
width: 33.3333%;
padding-right: 10px;
padding-left: 10px;
float: left;
}
上代码:
查看 padding实现的拆分间隔网格 by Zell Liew (@zellwk).
在我们继续开始之前,如果你用padding分拆间隔的话给你一个小警告。仔细看一下Codepen上的标签,你就会发现我在.grid-item里添加了一个额外的子元素,如果组件包含背景或者边框的话这个是必须的。
这是因为背景会显示在padding属性上。下面这幅图应该可以解释为什么(希望可以),展示了background
和其他属性间的关系。
背景显示在padding上
我会用什么?
当我两年前开始写栅格时,我几乎手写栅格,通过自上而下的分析设计,内置一个混合系统。在那样的系统/方法里,列宽和间隔值我都是使用的百分比。
在那时候,我喜欢单边设置间隔的简单。对我来说,这样有较少的认知负荷,因为我实在不擅长数学。额外的间隔÷2
的计算就可以让我迅速放弃。
我还是感激我继续那样做。尽管CSS看起来比拆分间隔更复杂,但我强迫自己学习恰当使用nth-child。我还知道了编写 mobile-first CSS的重要性,据我所知,无论对于初学还是有经验的开发者,这仍然是主要障碍。
然而,如果你现在让我选择,我会选择拆分间隔而不是单边间隔,因为这样CSS会更简单。同时,我更愿意用margin而不是padding,因为这样可以有更干净的标签。(但是padding更容易计算,所以在剩下的篇幅中我会继续使用padding的方式。)
Step 6: 创建调试网格
当你开始时,有一个控制网格帮你调试布局会尤其管用。它可以帮助你确保你是在正确地创建东西。
在这一点上,我只知道一个蹩脚的创建调试网格的方式。就是创建HTML元素,再在上面添加样式CSS。HTML如下:
<div class="fixed-gutter-grid">
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
<div class="column"></div>
</div>
针对调试网格的CSS如下(为了减少调试网格的标签,我使用的是用margins拆分间隔):
.column {
width: calc((100% - 20px * 12) / 12);
height: 80px;
margin-right: 10px;
margin-left: 10px;
background: rgba(0, 0, 255, 0.25);
float: left;
}
查看代码 Fixed gutter debug grid by Zell Liew (@zellwk) 。
(边注:Miriam 和 Robson正打造一个Susy v3上的SVG-背景图片调试网格。这令人超级鸡冻啊,因为你可以用一个简单的函数就能创建调试网格!)
Step 7: 创建可变的布局
下一步就是根据内容创建可变的布局。这就是CSS栅格系统闪光的地方。替代了通过写重复的栅格类来创建布局,你可以根据内容创建合理的类名。
比如,假设你有一个只用于客户文章的网格布局。桌面上布局看起来像这样:
仅用于客户文章的网格布局示例
标签组织可以是这样:
<div class="l-guest-article">
<div class="l-guest"> <!-- Guest profile --></div>
<div class="l-main"><!-- main article--></div>
<div class="l-sidebar"><!-- sidebar widgets--></div>
</div>
看起来很舒服。现在我们有12列。每一列的宽是8.333%(100 ÷ 12)
。
.l-guest
的宽度是两列。所以,就是8.333% 乘2,就这么简单。以此类推。
这里,我建议使用Sass这样的预处理器,这样可以轻松地用percentage
函数计算列宽,就不用手动计算了:
.l-guest-article {
@include clearfix;
.l-guest {
// Ahem. More readable than 16.666% :)
width: percentage(2/12);
padding-left: 10px;
padding-right: 10px;
float: left;
}
.l-main {
width: percentage(7/12);
padding-right: 10px;
padding-left: 10px;
float: left;
}
.l-sidebar {
width: percentage(3/12);
padding-right: 10px;
padding-left: 10px;
float: left;
}
}
查看代码 Content-sidebar-layout with fixed-gutter grid by Zell Liew (@zellwk) .
你可能已经发现到现在有很多重复的代码。我们可以通过抽出公共的部分到一个单独的选择器比如.grid-item
里来优化它。
.grid-item {
padding-left: 10px;
padding-right: 10px;
float: left;
}
.l-guest-article {
.l-guest { width: percentage(2/12);}
.l-main { width: percentage(7/12);}
.l-sidebar { width: percentage(3/12); }
}
干净多了 :)
Step 8: 使布局响应
最后一步就是让布局可响应。假设我们的客户文章布局按照下面的方式响应:
对于不同视口文章布局如何响应
我们标签不用变。我们现在已经有了可能是最易理解的布局。所以,要改变的完全应该是CSS。
当写响应式布局的CSS时,我强烈建议你写mobile first css,因为它能让你的代码更简单优雅。我们可以开始优先对手机端布局写CSS。 上代码:
.l-guest-article {
.l-guest { /* nothing goes here */ }
.l-main {
margin-top: 20px;
}
.l-sidebar {
margin-top: 20px;
}
}
我们不需要再做什么,因为每个组件默认是占满宽度。然而,我们可以添加一些上边距到最后两项上,从而使元素相互分开。
接下来,我们移动到平板端的布局。
对于这个,假设在分界点是700px时触发。.l-guest
应该是4列(一共12列), .l-main
和.l-sidebar
每个应该是8列。
这里,我需要去掉.l-main
的margin-top
属性,因为它需要和.l-guest
排成一行。
而且,如果我们设置.l-sidebar
为8列的宽度,那它会自动浮动到第二行,因为第一行没有足够的空间可以容纳它。既然显示在第二行了,我们也需要在.l-sidebar
上添加一些左边距来将它放到合适的位置;要不然,我们将它浮动在右边。(我还是把它浮动在右边吧,这样不需要什么计算)。
最后,一旦我们浮动这些网格项,网格容器就需要一个clearfix来清除它们。
.l-guest-article {
@include clearfix;
.l-guest {
@media (min-width: 700px) {
width: percentage(4/12);
float: left;
}
}
.l-main {
margin-top: 20px;
@media (min-width: 700px) {
width: percentage(8/12);
margin-top: 0;
float: left;
}
}
.l-sidebar {
margin-top: 20px;
@media (min-width: 700px) {
width: percentage(8/12);
float: right;
}
}
}
最后一点,让我们来看看桌面布局。
对这种布局,我们假设触发的分界点是1200px。.l-guest
应该是总宽的2/12,.l-main
应该是7/12,.l-sidebar
是3/12.
我们要做的就是在每个网格项上创建一个新的媒体查询,再根据需要改变宽度。得注意也需要去掉',l-sidebar
上的margin-top属性。
.l-guest-article {
@include clearfix;
.l-guest {
@media (min-width: 700px) {
width: percentage(4/12);
float: left;
}
@media (min-width: 1200px) {
width: percentage(2/12);
}
}
.l-main {
margin-top: 20px;
@media (min-width: 700px) {
width: percentage(8/12);
margin-top: 0;
float: left;
}
@media (min-width: 1200px) {
width: percentage(7/12);
}
}
.l-sidebar {
margin-top: 20px;
@media (min-width: 700px) {
width: percentage(8/12);
float: right;
}
@media (min-width: 1200px) {
width: percentage(3/12);
margin-top: 0;
}
}
}
最终布局的代码: 查看guest-article layout with fixed-gutter grid (final) by Zell Liew (@zellwk).
(噢,顺便说一下,你也可以用Susy实现这些效果。只要记住设置 间隔位置为inside-static
)
总结
哇哦!文章是挺长的。写这篇文章我也是写到死了几次了。(感谢你能从头读到尾。我希望你看下来没有吐血)(ps:其实翻译的有点快吐血了)
正如你看到的,创建一个响应式栅格系统的步骤是相对简单直观的。人们最容易混乱的部分是第五步(决定间隔位置)以及第8步(使布局可响应)。