Web布局非常困难。它如此困难的原因是自从使用CSS来完成Web布局开始就并没有真正的完成复杂的Web布局。虽然我们使用很多技术手段能实现Web的固定布局,但是这些方法在响应式设计中又出现很多局限性与不足。不过值得庆幸的是,我们有了Flexbox模块,或许还有很多读者已经开始使用CSS Grid和Box Alignment模块做Web布局。
在这篇文章中,我将解释如何将这些组合在一起使用,你会发现通过对Flexbox的理解,你也能更好的理解Grid布局。
CSS Grid布局目前仅在Google Chrome Canary、Webkit Safari、Firefox Nightly浏览器可以看到效果。如果你需要在Chrome、Safari、Firefox或者Opera浏览器中看到CSS Grid布局的效果,需要通过浏览器的 flag
或者 available
开启开发者实验属性。我尽量在维护一个有关于浏览器对 CSS Grid布局的支持列表 。
display
的新属性值 grid
和 flex
是 display
的两个新属性值。使用 display:flex
可以让一个元素变成Flex容器,而使用 display:grid
可以让一个元素变成Grid容器。
当我们这样做的时候,Flex容器或Grid容器的子元素就分别变成了Flex项目和Grid项目。它们也直接使用了Flex或Grid项目的初始值。
在第一个示例中,设置了 display:flex
的容器有三个子元素。我们需要做的是使用Flexbox。
除非给Flex容器添加不同的属性,不然就会使用Flex容器的初始值(默认值):
flex-direction:row
flex-wrap: no-wrap
align-items: stretch
justify-content: flex-start
Flex项目的初始值:
flex-grow: 1
flex-shrink: 0
flex-basis: auto
本文后面的内容将会介绍这些属性和值是如何工作的。现在,你需要做的就是在父容器中设置 display:flex
,Flexbox就会开始工作:
有关于Flexbox更多的介绍,可以点击这里。
使用 display: grid
声明一个网格容器。以便我们可以看到网格的渲染行为,这个例子中拿了五个 .card
做为示例。
只显式设置 display: grid
并不会有明显的效果,但是Grid容器的所有子元素都变成了Grid项目。通过它们渲染成单列多行(隐式的行)的一个网格,
我们可以进一步的使用网格特性创建一些列,让它看起来更像一个网格。这里我们使用 grid-template-rows
属性。
在下一个示例中,使用一个新的单位创建一个三列等宽的网格。 fr
单位可以让可用空间按一个的比例进行划分。你可以看到我们的网格已经创建了,第个单元格显式定义为网格的列,但网格的行还是隐式的创建。当网格的单元格填满网格容器时,单元格会自动创建新的一行,让更多的网格单元格可以排列。
同样的,它们也有一些默认的行为。我们还没有显式的给网格项目设置对应的属性时,网格的每个单元格将会按自己的默认行为在网格中排列。网格容器和网格项目都具有自身的初始值。下面是网格容器的初始值:
grid-auto-flow: row
grid-auto-rows: auto
align-items: stretch
justify-items: stretch
grid-gap: 0
这些初始值意味着我们的网格项目放置在网格的每个单元格中,并且一行一行的排列。因为我们有一个三列的网格,第三列后面的单元格将会延伸到下一行(创建新的一行)。行的尺寸是自动计算的,所以它将会自动伸缩以用来适应内容。网格项目将在两个方向进行延伸(横向和纵向),用以填充网格区域。
有关于Grid更多的介绍,可以点击这里。
在前面的两个简单示例中,已经看到了盒子对齐模块(Box Alignment Module)的身影。Box Alignment Level 3规范本质上它义了Flexbox容器里项目分布方式(也可以用于其他模块)。也就是说,如果你已经在使用Flexbox的话,那么你其实已经开始在使用盒子对齐模块相关属性。
接下来我们一起看看Flexbox和Grid模块中是怎么使用Box Alignment Module,它又能帮我们解决什么样的问题。
有的时候使用老式的表格布局可以很容易创建等高列,但是使用定位和浮动布局就极难实现等高列。下面提出的例子,我们的 .card
里面包含了不同的内容。我们没有办法让其它的 .card
和第一个 .card
有一样的高度,因为它们彼此之间没有关系。
当我们给父容器显式的设置了 display
的属性值为 grid
或 flex
时,它们的子元素就有了互相的关系。这种关系可以很好的使用Box Alignment Module的属性,可以很简单的使每列的高度相等。
在下面的Flexbox的示例中,我们的每个Flex项目有不同的内容。虽然每个基线上都有背景颜色,但它的渲染效果并不像浮动元素。这些Flex项目显示在一行,并且通过 align-items
属性来控制项目对齐方式。主要使用这个属性的初始值(默认值) stretch
来拉伸每个项目,从而创建等高列效果。
在网格布局中我们看到了相同的效果。下面是一个简单的网格布局,包含了两列,一个侧边栏和主要内容。我们再次使用了 1fr
这个单位。侧边栏使用可用空间的 1fr
,而主内容使用可用空间的 3fr
。侧边栏背景颜色和主内容底部对齐。再一次使用了 align-items
的默认值 stretch
。
我们已经了解了Grid和Flexbox中如何使用 align-items
的默认值 stretch
。
在Flexbox中,当我们使用 align-items
属性,可以控制Flex项目在Flex容器侧轴方向上的对齐方式。不过我们可以使用 flex-direction
属性来定义Flex项目在Flex容器主轴上的对齐方式。在第一个示例中,主轴是 row
,Flex项目的高度会延伸到Flex容器的高度。在这种情况,Flex容器的高度是由Flex项目内容最多的一项来决定。
我们可以显式的给Flex容器定义一个高度。
还可以使用其他值为替代默认的 stretch
值:
flex-start
flex-end
baseline
stretch
使用 justify-content
控制主轴上的对齐方式。其默认值是 flex-start
,这也就是说,为什么在示例中看到的效果,Flex项目都是左对齐。除此之外,我们还可以使用下列的值:
flex-start
flex-end
center
space-around
space-between
共中 space-around
和 space-between
尤其有趣。使用 space-between
可以很好的让Flex项目之间的间距可以得到均匀的分布。
space-around
也有点类似,除了第一个和最后的两个Flex项目,其他相邻之间的Flex项目可以均匀分布Flex容器中剩余下的空间。
Codepen中的这个示例可以看到这些属性和值的使用效果:
我们也可以把Flex项目不按行的方式排列,而是按列的方式排列。如果把 flex-direction
设置为 column
,Flex容器的主轴就变成列,而侧轴就变成了行。 align-items
的默认值依旧是 stretch
,并且Flex项目在侧轴(行的方向)拉伸占满容器的宽度。
如果想让项目从Flex容器开始位置对齐,我们可以使用 flex-start
。
也可以使用 justify-items
,它包括 space-between
和 space-around
。容器需要有一个足够的高度,你才能看到相应的效果。
在网格布局中,渲染行为有点类似,除此之外还可以定义网格区域内网格项目的对齐方式。在Flexbox中,我说讨论的是主轴(Main Axis)和侧轴(Cross Axis);在Grid中,使用 block 或者列轴( Column Axis )来定义列,使用 inline 或者行轴( Row Axis )来定义行。有关于这方面的详细介绍,可以阅读相关的 规范文档 。
我们可以使用盒子对齐模块(Box Alignment)规范中定义的属性和值来控制网格区域内内容的对齐方式。
网格区域是指一个或多个单元格。在下面的示例中,有一个四行四列的网格。轨道之间有一个默认的 10px
间距,并且基于网格线创建了三个网格区域。后面的我们会介绍怎么使用网格线定义网格的区域,这里使用 /
符,前面的值表示开始,后面的值表示结束。
虚线边框在一个背景图像上,能更好的帮助我们看到定义的网格区域。因此,在第一个示例中,每个区域的 align-items
(列轴方向)和 justify-items
(行轴方向)都使用了其默认值 stretch
。这也意味着,内容区域将会自动延伸填充整个网格区域。
在第二个示例中,我把网格容器的 align-items
值改为 center
。我们也可以在每个网格项目上设置 align-self
值。在这个示例中,我在所有网格项目上设置的 align-self
的值为 center
,但在第二个item上依旧设置的是 stretch
。
在第三个示例中,我改变了 justify-items
和 justify-self
的值,分别设置为 center
和 stretch
。
在上面的示例中,了解了怎么控制网格区域的内容对齐方式以及如何通过开始网格线和结束网格线定义网格的区域。
我们也可以在网格容器上控制网格内元素的对齐方式。在本例中,我们使用了类似于Flexbox中的 align-content
和 justify-content
两个属性。
在第一个示例中,我们看到网格的对齐模式是默认的,网格的行和列定义了绝对单位,并且占用了较少的容器空间。默认值( align-content
和 justify-content
)是 start
。
如果把值换成 end
,网格轨道就会移动到右下角。
和Flexbox类似,我们可以使用 space-around
和 space-between
两个值。这可能会导致一些我们并不想要的行为,比如说改变 grid-gap
的值。下图就是Codepen中的第三个示例,看到的效果类似于Flexbox中一样,网格间距会随着容器可用空间做出变化。
除此之外,如果网格轨道跨越多个间距时,固定大小的网格轨道将会获得额外的空间。比如示例中的第二个元素和第四个元素明显的变得更宽,第三个元素更高。因为它们有额外的空间分配到间网格的间距中(合并单元格)。
也可以把这两个属性的值都设置为 center
。看到的效果就类似于最后一个示例。
在Flexbox和Grid中我们能很好的控制元素对齐,而且他们的工作方式普遍一致。在响应式中可以通过调整个别项目或组项目组来防止它们之间的重叠。
上一节中主要了解了对齐方式。Box Alignment属性是Grid和Flexbox布局领域的一部分,相关的规范也出来了,那么在响应式方面它是如何做的呢?比如其值 space-between
、 space-around
和 stretch
的响应式能力如何?
除此之外,还有更多。响应式设计往往是要保持一定比例的。当我们计算列的响应式设计,一般是按这样的公式来计算 target ÷ context
。@Ethan Marcottes在介绍 流式网格的文章 中有提到,我们要操持绝对宽度设计的原始比例。Flexbox和Grid布局可以让我们使用更简单的方式来处理设计中的比例。
Flexbox可以让我们更灵活的控制内容。当我们使用 space-between
值时,容器可用空间可以均匀的分配到Flex项目上。首先,Flex项目会按所占用的空间进行计算,然后容器所剩余的空间(可用空间)将会均匀的划分到每一个Flex项目中。为了更好的控制Flex项目,我们还可以使用下面的一些属性:
flex-grow
flex-shrink
flex-basis
这三个属性简写起来对应的就是 flex
。如果在Flex项目中设置 flex: 1 1 300px
,那么 flex-grow
的值为 1
,表示Flex项目可的扩展; flex-shrink
的值为 1
,表示Flex项目可以缩小; flex-basis
的值为 300px
(相当于Flex项目在分配Flex容器剩余空间之前的一个默认尺寸)。如果把这个运用到卡片上的布局,就能看到类似下面这样的效果:
这里一行有三张卡片,而且 flex-basis
的值是 300px
。如果Flex容器的宽度大于 900px
,那么剩余的空间将会分成三等份,分布到每一个Flex项目中。这是因为我们设置了 flex-grow:1;
,以便Flex项目可以扩大。同时,也设置了 flex-shrink
的值为 1
,这也意味着,如果可用的空间分配给三个Flex项目不到 300px
时,那么每个Flex项目空间也会被收缩。
如果我们想要让不同的Flex项目扩展的比例不同,只需要在不同的Flex项目中设置 flex-grow
的不同值。比如,想让第一个Flex项目可用空间是分布空间的三倍,我们只需要设置 flex-grow:3;
。
有效空间是在 flex-basis
之后做的分配。这也就是为什么第一个Flex项目不是其他Flex项目的三倍(宽度),而仅仅是占容器剩余空间的三份。如果 flex-basis
设置为 0
,你可以看到一个很大的变化,比如下面的示例,Flex项目宽度是按比例分配容器的宽度。
有一个 Flexbox的测试工具 ,可以很好的帮助你理解这些值。在测试工具的输入框中输入不同的值,它会根据你输入的值做一些变化,并且告诉详细告诉你计算的一个过程。
如果你使用 auto
做为 flex-basis
的值,它将使用任何值作为 flex-basis
值,设置在每个Flex项目上。如果没有显式的设置大小值,那么其默认值是和内容宽度一样。因此,使用 auto
是非常有用的,可以用于可重用的组件中,你可能只需要在一个Flex项目中设置大小。你可以使用 auto
值和在需要明确定义大小的Flex项目中设置值,Flexbox会根据它们做相应的计算。
在接下来的示例中,我设置了 flex-basis
的值为 auto
。然后再把第一张卡片的宽度设置为 350px
。现在第一张卡片的 flex-basis
就变成了 350px
,用于解决如何分配空间。另外两张卡片的 flex-basis
就是基于其内容的宽度。
如果我们回到最初设置的 flex: 1 1 300px;
,并且在Flex容器上添加 flex-wrap:wrap;
,那么Flex项目会保持尽可能的靠近 flex-basis
的值。如果我们有五张卡片,那么第一行会放置三个上卡片,接下来的一行放置两张卡片。Flex项目也会扩展,使我们得到两个相同宽度的卡片。如果缩小浏览器的视窗宽度,可以看到每行两张卡片,最后一张卡片放在第三行,而且宽度变成和容器一样大小。当你继续缩小视窗,可以看到每个卡片占据一行,宽度和容器宽度一样。
我们经常会问一个问题“我们怎么把最后面的一个Flex项目排到最前面,而且最底部还留一个间距?” 答案是,Flexbox没办法做到。这样的效果需要一个Grid布局。
对于网格布局,正如我们前面看到的,它有一个概念,可以创建列和行的网格轨道用于网格项目的定位。当我们创建一个灵活的网格布局时,可以在网格容器上设置网格轨道的比例,而不是像Flexbox在Flex项目中设置比例。我们可以使用 fr
来创建网格单元格。这个单位工作方式类似于 flex-basis
值为 0
时的 flex-grow
方式。它可以将网格容器中可用空间分配到网格项目中。
在下面的这个示例中,第一个网格轨道设置 2fr
,其他两个轨道设置的都是 1fr
。也就是说,把网格容器分成了四份,其中第一个网格轨道占了两份,其他两个网格轨道各占一份。
在网格布局中,绝对单位和 fr
单位混合使用都是有效的。比如下面的这个示例,我们有一个 2fr
, 1fr
和 300px
的网格轨道。首先网格容器空间将会剔除绝对宽度的网格轨道,然后将网格容器剩余的空间分成三等分,其中 2fr
的网格轨道占两份, 1fr
的网格轨道占一份。
从示例中你可以看到,网格项目填充到了定义好的网格轨道中,而不像Flexbox中的示例会自动分配行。这主要是因为,网格布局创建了一个二维布局,然后在把网格项目放进来。Flexbox,让我们根据内容适合度来控制,看多少适合在一个行或列的维度中放置,然后将额外的放到另一行或列。
什么都好,如果有一个方法可以在容器中尽可能的放置列,那将更完美。我们可以使用 grid
和 repeat()
。
在下一个示列中,我们将使用 repeat()
语法在容器中创建尽可能多的 200px
列。我使用 repeat
,并且给其设置关键词 auto-fill
和具体的尺寸( 200px
)来创建我想要重复的网格轨道的大小。
我们可以做得更好一点,把 fr
单位和绝对宽度结合起来,告诉创建的网格,尽可能多的放置 200px
的网格轨道(网格容器中放置尽可能多的接近 200px
的网格项目)。
简单的解释一下,给 repeat
设置了一个 auto-fill
值,告诉网格容器尽可能的填充网格项目, repeat
的第二个值 minmax(200px, 1fr)
是用来控制网格项目的宽度的。首先会根据网格容器的宽度进行计算,在容器中尽可能的放置更多个 200px
的网格项目,如果网格容器放置了尽可能多的 200px
网格项目之后,还有剩余空间,那么将会把网格容器剩余空间平均分配到每一个网格项目中。
使用这种方法布局,我们可以得到一个二维布局,其好处是具有灵活的轨道(行或列),因此不需要任何的媒体查询。这里我们也看到了Grid和Flexbox的不同之处。Flexbox是一维的布局,而Grid是二维的布局。
在Flexbox中,对于Flex项目定位并不能做得很好。我们可以通过设置 flex-direction
的值为 row
、 row-reverse
或者 column
、 column-reverse
来控制Flex项目的排列方向。设置 order
的值来设置Flex项目的顺序位置。
在网格布局中,我们可以准确的定义网格项目的位置。在上面的示例中,我们一直依赖于网格的自动排列规则定义网格项目。在下面的示例中,我们将基于网格线来定义网格项目在网格中的位置。
grid-column
和 grid-row
属性分别是 grid-column-start
, grid-column-end
和 grid-row-start
, grid-row-end
的简写属性。其值使用 /
来分隔,其中 /
前面的值表示开始的值,后面的值表示结束的值。
你也可以创建网格线名称。当你创建网格容器的时候就创建了网格线,这些网格线你使用名称来命名,然后在网格项目中使用声明好的网格线名称,而不是使用网格线的索引值。
还可以给多个网格线命名相同的名称,然后通过网格线的名称和索引值一起使用。
还可以使用 span
关键词,用来合并多个行或列。这种方式用于创建组件放在不同的位置非常有用。在下面的示例中,我想创建的一些元素中合并六个列,其他的合并三个列。我将使用自动排列来控制网格项目。当碰到类名为 .wide
的,开始的值为 auto
,结束的值为 span 2
。因此,开始于正常的网格线上,然后根据自动排列的规则,合并两个列。
使用自动排列的规则,在网格布局中有可能会造成一些空白网格。默认情况之下,网格一旦留有缺口,其它网格项目是无法填充进去的。除非将 grid-auto-flow
设置为 dense
。这种情况下,网格项目就会自动填充上去。
除了前面介绍的方法之外,还可以通过 grid-template-areas
的值来创建一个可视的网格布局。如果要做到这一点,首先需要知道网格容器子元素(网格项目)放置在哪个位置。
更有意思的是,也可以把 grid-template-areas
的值命名为ASCII码。如果你想基于媒体查询重新定义布局,只需要在不同的媒体查询条件下改变这个属性的值。
从这个示例中,你可以看出来,如果没有的地方,我们采用一个或者一个系列的 .
来描述,表示它们之间没有空格。让一个元素可以起到合并单元格的作用。
不管是在Flexbox布局中还是Grid布局中,都需要非常小心的避开使用这些方法来重新给内容排序。 Flexbox规范 中是这样描述的:
Authors must use order only for visual, not logical, reordering of content. Style sheets that use order to perform logical reordering are non-conforming.
Grid中是这样做出描述 的:
Grid layout gives authors great powers of rearrangement over the document. However, these are not a substitute for correct ordering of the document source. The order property and grid placement do not affect ordering in non-visual media (such as speech). Likewise, rearranging grid items visually does not affect the default traversal order of sequential navigation modes (such as cycling through links).
在这两种情况之下,到目前为止,重新排序都只是视觉上的效果,它并不会改变文档流的逻辑顺序(默认顺序)。此外,我们需要小心用户访问页面的时候使用键盘。原因非常的简单,改变顺序有可能让你的用户在访问的时候,突然有错乱的顺序。比如人家在访问导航,你改变顺序之后,有可能把人家直接带到了页面的底部。
只通过一篇文章没办法涵盖到Grid和Flexbox的方方面面。我的目的是只是展示了他们之间的异同。从而证明,这些规范可能让我们构建一个全新的布局系统,能让我们更好的建设一个网站或者应用程序。
目前我们使用较多的是Flexbox,但Grid也将慢慢的得到支持。Flexbox最初出现的时候,开发人员用于生产需要添加浏览器前缀。目前为止对于Grid要开启浏览器实验性选项,你就可以看到对应的效果。对于Grid规范现在处于候选人推荐状态。当Grid在明年年初得到更友好的支持时,它将是处于一个跨浏览器兼容状态。
除了文章中的示例之外,下面还有一大堆的详细资料可以供你参考。我特别喜欢写这方面的示例,实现各式各样的布局效果。如果你有其他特殊的布局,可以告诉我,我非常喜欢接受这方面的挑战。
本文根据 @Rachel Andrew 的《 CSS Grid, Flexbox And Box Alignment: Our New System For Web Layout 》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处: https://www.smashingmagazine.com/2016/11/css-grids-flexbox-and-box-alignment-our-new-system-for-web-layout/ 。
常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《 图解CSS3:核心技术与案例实战 》。