Web布局新系统:CSS Grid,Flexbox和Box Alignment

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的新属性值

gridflexdisplay的两个新属性值。使用display:flex可以让一个元素变成Flex容器,而使用display:grid可以让一个元素变成Grid容器。

当我们这样做的时候,Flex容器或Grid容器的子元素就分别变成了Flex项目和Grid项目。它们也直接使用了Flex或Grid项目的初始值。

display:flex

在第一个示例中,设置了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

使用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

在前面的两个简单示例中,已经看到了盒子对齐模块(Box Alignment Module)的身影。Box Alignment Level 3规范本质上它义了Flexbox容器里项目分布方式(也可以用于其他模块)。也就是说,如果你已经在使用Flexbox的话,那么你其实已经开始在使用盒子对齐模块相关属性。

接下来我们一起看看Flexbox和Grid模块中是怎么使用Box Alignment Module,它又能帮我们解决什么样的问题。

等高列

有的时候使用老式的表格布局可以很容易创建等高列,但是使用定位和浮动布局就极难实现等高列。下面提出的例子,我们的.card里面包含了不同的内容。我们没有办法让其它的.card和第一个.card有一样的高度,因为它们彼此之间没有关系。

当我们给父容器显式的设置了display的属性值为gridflex时,它们的子元素就有了互相的关系。这种关系可以很好的使用Box Alignment Module的属性,可以很简单的使每列的高度相等。

在下面的Flexbox的示例中,我们的每个Flex项目有不同的内容。虽然每个基线上都有背景颜色,但它的渲染效果并不像浮动元素。这些Flex项目显示在一行,并且通过align-items属性来控制项目对齐方式。主要使用这个属性的初始值(默认值)stretch来拉伸每个项目,从而创建等高列效果。

在网格布局中我们看到了相同的效果。下面是一个简单的网格布局,包含了两列,一个侧边栏和主要内容。我们再次使用了1fr这个单位。侧边栏使用可用空间的1fr,而主内容使用可用空间的3fr。侧边栏背景颜色和主内容底部对齐。再一次使用了align-items的默认值stretch

Flexbox中的对齐

我们已经了解了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-aroundspace-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-betweenspace-around。容器需要有一个足够的高度,你才能看到相应的效果。

Grid布局中的对齐

在网格布局中,渲染行为有点类似,除此之外还可以定义网格区域内网格项目的对齐方式。在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-itemsjustify-self的值,分别设置为centerstretch

在上面的示例中,了解了怎么控制网格区域的内容对齐方式以及如何通过开始网格线和结束网格线定义网格的区域。

我们也可以在网格容器上控制网格内元素的对齐方式。在本例中,我们使用了类似于Flexbox中的align-contentjustify-content两个属性。

在第一个示例中,我们看到网格的对齐模式是默认的,网格的行和列定义了绝对单位,并且占用了较少的容器空间。默认值(align-contentjustify-content)是start

如果把值换成end,网格轨道就会移动到右下角。

和Flexbox类似,我们可以使用space-aroundspace-between两个值。这可能会导致一些我们并不想要的行为,比如说改变grid-gap的值。下图就是Codepen中的第三个示例,看到的效果类似于Flexbox中一样,网格间距会随着容器可用空间做出变化。

除此之外,如果网格轨道跨越多个间距时,固定大小的网格轨道将会获得额外的空间。比如示例中的第二个元素和第四个元素明显的变得更宽,第三个元素更高。因为它们有额外的空间分配到间网格的间距中(合并单元格)。

也可以把这两个属性的值都设置为center。看到的效果就类似于最后一个示例。

在Flexbox和Grid中我们能很好的控制元素对齐,而且他们的工作方式普遍一致。在响应式中可以通过调整个别项目或组项目组来防止它们之间的重叠。

默认响应式

上一节中主要了解了对齐方式。Box Alignment属性是Grid和Flexbox布局领域的一部分,相关的规范也出来了,那么在响应式方面它是如何做的呢?比如其值space-betweenspace-aroundstretch的响应式能力如何?

除此之外,还有更多。响应式设计往往是要保持一定比例的。当我们计算列的响应式设计,一般是按这样的公式来计算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单位混合使用都是有效的。比如下面的这个示例,我们有一个2fr1fr300px的网格轨道。首先网格容器空间将会剔除绝对宽度的网格轨道,然后将网格容器剩余的空间分成三等分,其中2fr的网格轨道占两份,1fr的网格轨道占一份。

从示例中你可以看到,网格项目填充到了定义好的网格轨道中,而不像Flexbox中的示例会自动分配行。这主要是因为,网格布局创建了一个二维布局,然后在把网格项目放进来。Flexbox,让我们根据内容适合度来控制,看多少适合在一个行或列的维度中放置,然后将额外的放到另一行或列。

什么都好,如果有一个方法可以在容器中尽可能的放置列,那将更完美。我们可以使用gridrepeat()

在下一个示列中,我们将使用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的值为rowrow-reverse或者columncolumn-reverse来控制Flex项目的排列方向。设置order的值来设置Flex项目的顺序位置。

在网格布局中,我们可以准确的定义网格项目的位置。在上面的示例中,我们一直依赖于网格的自动排列规则定义网格项目。在下面的示例中,我们将基于网格线来定义网格项目在网格中的位置。

grid-columngrid-row属性分别是grid-column-startgrid-column-endgrid-row-startgrid-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:核心技术与案例实战》。

如需转载,烦请注明出处:http://www.w3cplus.com/css/css-grids-flexbox-and-box-alignment-our-new-system-for-web-layout.html

返回顶部