Grid 布局
Grid布局应该是迄今为止最全面最强大的布局方式。从九宫格到圣杯布局、再到响应式设计,Grid提供了一套完美的解决方案。效果说起来很诱人,但学起来还是需要点儿时间,因为属性超级多,而且每个都有新花样,需要慢慢消化。ps: 多图预警,长篇预警,高能预警。
Introduction
用过BootStrap之类的应该对Grid不陌生,BootStrap3划分栅格主要使用百分比
,顺便去看了一下第四代,发现使用的是弹性布局了。
不知道大家看到一个小细节没,打开Chrome开发者工具,选择查看元素,将鼠标移动到html页面,发现浏览器自动给栅格标上了虚线,看下图。
既然是写CSS3相关的属性,按惯例先去Can I Use了解一下浏览器的支持情况,发现主流浏览器都能完美使用了。
Terminology
和弹性布局
一样,Grid布局也有一些独特的术语,这些务必要理解清楚。
Grid Container
栅格容器是栅格项(Grid Items)的父级元素,也就是那个需要定义display: grid;
的那个元素。看下面这个例子,class为grid-container
的那个div就是一个栅格容器。
<div class="grid-container"> <div class="grid-item grid-item-1"></div> <div class="grid-item grid-item-2"></div> <div class="grid-item grid-item-3"> <p>我不是grid-container的直接子元素</p> </div> </div>
Grid Item
栅格项是栅格容器的直接子元素
,注意直接
这两个字,因此上面代码示例中class为grid-item
的元素是栅格项,而里面的p
标签则不是。
Grid Line
栅格线是构成网格结构的分界线:垂直的叫做列栅格线(column grid lines)
,水平的叫做行栅格线(row grid lines)
,栅格线的概念很重要,后面定义grid-column
和grid-row
都会直接用到栅格线的概念,如下图黄线即为一条栅格线。
Grid Track
栅格轨道是由两条相邻
网格线构成的一个单行
或单列
的区域。注意相邻
这个概念,从词法来说,轨道
一般都是单条的,如下黄色区域则为一条栅格轨道。
Grid Cell
要注意和Grid Item
的区别,一个Grid Cell包含其对应的Grid Item和Grid Item里面的内容,如下图黄色区域即为一个栅格元。
Grid Area
最后一个术语则是栅格区域,可以说栅格轨道和栅格区域是包含和被包含的关系,如下图的黄色区域就是行栅格线1和3与列栅格线1和3之间圈出来的网格区域。
List of Grid Attributes
List of Grid Container Attributes
-
display
-
grid-template-columns
-
grid-template-rows
-
grid-template-areas
-
grid-template
-
grid-column-gap
-
grid-row-gap
-
grid-gap
-
justify-items
-
align-items
-
justify-content
-
align-content
-
grid-auto-columns
-
grid-auto-rows
-
grid-auto-flow
-
grid
List of Grid Item Attributes
-
grid-column-start
-
grid-column-end
-
grid-row-start
-
grid-row-end
-
grid-column
-
grid-row
-
grid-area
-
justify-self
-
align-self
似曾相识吧,如justify-items
、align-items
在弹性布局中都是核心概念,所以Grid布局则是一个更加全面的布局模式。
ps: 看到这么多属性头都大了,突然想到某考研名师的名言:
BUT! 考虑到梦想、钱(大误)前途。
嗯,真香...(手动王境泽.jpg)
Grid Container Attributes
首先一一介绍栅格容器的各个属性。
display
display: grid | inline-grid
CSS Grid 布局完全指南(图解 Grid 详细教程)还讲到有一个subgrid
的属性值,是在Grid布局里嵌套Grid布局,不过我的WebStorm提示没有这个属性值,那就忽略好了,感觉也没什么卵用。
当容器定义了grid布局之后,容器元素上定义的column
,float
,clear
, vertical-align
将失效,注意是容器元素,这些属性不会影响Grid Cell, 亲测,嗯。
grid-template-columns / grid-template-rows
这个属性定义Grid Item的大小,有两个属性值:一个是track-size
,另一个是line-name
。
.grid-container { grid-template-columns: <track-size> ... | <line-name> <track-size> ...; grid-template-rows: <track-size> ... | <line-name> <track-size> ...; }
track-size
track-size
单位很自由,可以是px、em、rem、vw、vh、百分数、auto等等,特别注意这个auto
,看下面这个代码,假设grid-container
外面还包着一层grid-wrapper
,宽度是500px,那么grid-template-columns
属性的auto
就会被计算成500-300-100=100px
.grid-wrapper { width: 500px; .grid-container { display: grid; grid-template-columns: 300px auto 100px; } }
还有一种情况是grid-container
的总宽或总高大于了grid-wrapper
的总宽或总高,看下面代码。
.grid-wrapper { width: 500px; .grid-container { display: grid; grid-template-columns: 300px 200px 100px; } }
这里的解释是个人的想法,留个坑,看下图。
line-name
这个属性值就比较骚气了,可以定义Grid Line的名称,注意要用中括号
包起来,第一次见。可以看一个综合示例。
.grid-container { display: grid; grid-template-columns: [first] 40px [line2] 50px [line3] auto [col4-start] 50px [five] 40px [end]; grid-template-rows: [row1-start] 25% [row1-end] 100px [third-line] auto [last-line]; }
更骚气的是,一条Grid Line还可以有多个名称,看代码,这里的第二条row-grid-line将会有两个名字,分别是row1-end
和row2-start
。
.grid-container { display: grid; grid-template-rows: [row1-start] 25% [row1-end row2-start] 25%; }
BUT! 还没骚完...还有一个repeat语法,看下面的代码。
.grid-container { display: grid; grid-template-columns: repeat(3, 33.33333vw [col]); grid-template-rows: repeat(3, 33.33333vh [row]); }
卧槽,什么概念,三句话搞了一个视口九宫格!想想用传统方式写个九宫格,甚至用弹性布局写个九宫格,而如今...
最后... 还有一个单位... 叫fr...
fr允许你用等分网格容器剩余可用空间来设置Grid Track的大小,看代码。
总宽度是500px,分了三列,其中第一列占了100px,所以后两列占400px,后两列一共是4fr,因此1fr是100px,所以下面的例子等价于grid-template-columns: 100px 300px 100px;
.grid-wrapper { width: 500px; .grid-container { display: grid; grid-template-columns: 100px 3fr 1fr; grid-template-rows: repeat(3, 100px [row]); } }
蛤?你以为这样就完了?
Naive!
下面要讲到一个属性叫grid-column-gap / grid-row-gap
,这个东西很流弊,它用来设置每个Grid Item的间距。
设个间距有什么流弊的?margin
不就得了?
但是考虑一个常见的场景,看代码。这是一个常见的navbar,要使用margin-right
分隔的话,需要把最后一个li
的margin-right
设为0
.
<nav> <ul> <li>home</li> <li>blog</li> <li>music</li> <li>photo</li> <li>about</li> </ul> </nav> li { list-style-type: none; display: inline-block; margin-right: 10px; &:last-child { margin-right: 0; } }
BUT!grid-column-gap / grid-row-gap
只会在 列/行 之间创建间距,两侧不会有这个间距。看代码。
ul { display: grid; grid-template-columns: repeat(5, 1fr); grid-column-gap: 10px; li { list-style-type: none; display: inline-block; } }
当然这个例子不太好,目前实在也想不出太好的例子来,总之就是grid-column-gap / grid-row-gap
可以避免左右边界也给创建间隔。
那grid-column-gap / grid-row-gap
跟fr
有毛线关系?看下面这个例子。假设总宽度为500px,现在列分成了5份,每份占20%.
当没有间隔时,正好每个Grid Item的宽为100px
;
但有了10px的列间隔后,看下面的图,会发现每个Grid Item的宽仍为100px
,但右边溢出了一部分。
.grid-wrapper { width: 500px; .grid-container { display: grid; grid-template-columns: repeat(5, 20%); grid-template-rows: repeat(2, 100px [row]); grid-column-gap: 10px; }
当然你可以考虑使用计算属性,看代码。
.grid-wrapper { width: 500px; .grid-container { display: grid; grid-template-columns: repeat(5, calc((500px - 10px * 4) / 5)); grid-template-rows: repeat(2, 100px [row]); grid-column-gap: 10px; }
但是还是很麻烦,所以fr就派上了用场(卧槽前面居然这么多铺垫),以列这个方向为例,fr是总宽度减去固定Grid Track的宽度,如果有间隔,再减去间隔的总宽度,最后再去平分剩余宽度,看代码。完美解决。
.grid-wrapper { width: 500px; .grid-container { display: grid; grid-template-columns: repeat(5, 1fr); grid-template-rows: repeat(2, 100px [row]); grid-column-gap: 10px; }
grid-template-areas
这是用来定义模版区域的一个属性,有三个属性值,分别是:
- <grid-area-name>:由网格项的 grid-area 指定的网格区域名称
- .(点号) :代表一个空的网格单元
- none:不定义网格区域
还是直接看例子。
.grid-item-1 { grid-area: header; } .grid-item-2 { grid-area: main; } .grid-item-3 { grid-area: aside; } .grid-item-4 { grid-area: footer; } .grid-container { display: grid; grid-template-columns: 50px 50px 50px 50px; grid-template-rows: auto; grid-template-areas: "header header header header" "main main . aside" "footer footer footer footer"; }
整个栅格系统分了四列,每列宽50px,第一行四个全被定义成了header
;第二行从左右到右,main
占了列,第三列未定义
名称,所以用.
来占位,最后一个则被定义为aside
;最后一行则被footer
所占据。
看到这里,就有种模块化的味道了
,比如在class为grid-item-1
的部分去定义header
的相关属性;在class为grid-item-4
的部分去定义footer
的相关属性。
放一个我写的例子。
grid-template
这个属性是grid-template-rows
,grid-template-columns
,grid-template-areas
的合体版。
私以为本来就很复杂了,合起来更麻烦了,而且grid-template
不会重置隐式
栅格属性,即grid-auto-column
、 grid-auto-rows
、grid-auto-flow
,而且auto
又很常见,所以少用为妙,而且下面还有跟好用的办法。
grid-column-gap / grid-row-gap
不多说,上面已经介绍到了。这里重复一下,grid-column-gap / grid-row-gap
只会在 列/行 之间创建间距,两侧不会有这个间距。
grid-gap
这是grid-column-gap
和grid-row-gap
的合体写法,具体语法如下:
.grid-container { grid-gap: <grid-row-gap> <grid-column-gap>; }
看一个特殊情况:
.grid-container { display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(3, 100px [row]); grid-gap: 10px; }
当grid-gap
只写了一个值,这个值会被认作是grid-column-gap
的属性值,这个时候相当于grid-row-gap
没有定义,因此grid-row-gap
也会被赋给这个值,相当于grid-gap: 10px 10px;
.
因此,如果只想要列间距,就写成grid-gap: 0 10px;
即可。
justify-items
注:这个属性的细节,包括下面的align-items
就不详细写了,这一块和我以前写过的一篇文章弹性布局一模一样,那里对每个属性值都做了分析,这里只是简单贴一下介绍和属性值list.
justify-items为沿着行轴线(row axis) 对齐网格项(grid items) 内的内容。
-
start:将内容对齐到网格区域(grid area)的左侧
-
end:将内容对齐到网格区域的右侧
-
center:将内容对齐到网格区域的中间(水平居中)
-
stretch:填满网格区域宽度(默认值)
align-items
align-items为沿着列轴线(column axis) 对齐网格项(grid items) 内的内容。
-
tart:将内容对齐到网格区域(grid area)的顶部
-
end:将内容对齐到网格区域的底部
-
center:将内容对齐到网格区域的中间(垂直居中)
-
stretch:填满网格区域高度(默认值)
justify-content
此属性沿着行轴线(row axis) 对齐网格。
-
start:将网格对齐到 网格容器(grid container) 的左边
-
end:将网格对齐到 网格容器 的右边
-
center:将网格对齐到 网格容器 的中间(水平居中)
-
stretch:调整 网格项(grid items) 的宽度,允许该网格填充满整个 网格容器 的宽度
-
space-around:在每个网格项之间放置一个均匀的空间,左右两端放置一半的空间
-
space-between:在每个网格项之间放置一个均匀的空间,左右两端没有空间
-
space-evenly:在每个栅格项目之间放置一个均匀的空间,左右两端放置一个均匀的空间
align-content
此属性沿着列轴线(column axis) 对齐网格。
-
start:将网格对齐到 网格容器(grid container) 的顶部
-
end:将网格对齐到 网格容器 的底部
-
center:将网格对齐到 网格容器 的中间(垂直居中)
-
stretch:调整 网格项(grid items) 的高度,允许该网格填充满整个 网格容器 的高度
-
space-around:在每个网格项之间放置一个均匀的空间,上下两端放置一半的空间
-
space-between:在每个网格项之间放置一个均匀的空间,上下两端没有空间
-
space-evenly:在每个栅格项目之间放置一个均匀的空间,上下两端放置一个均匀的空间
grid-auto-columns / grid-auto-rows
此属性用于自动生成非grid-template-rows
或 grid-template-columns
创建的Grid Track。
有些绕,看例子,很显然会生成下图这样一个栅格系统。
.grid-container { display: grid; grid-template-columns: 60px 60px; grid-template-rows: 90px 90px; }
这里暂时用一下Grid Items的语法grid-column
和grid-row
,以区域2为例,它在垂直方向由第一根线
和第二根线
围成;在水平方向由第二根线
和第三根线
围成。因此区域2可以用一下代码表示:
.area-2{ grid-column: 1 / 2; grid-row: 2 / 3; }
回到正题,看下面代码,item-a
是在真正的栅格系统里,而item-b
不再原栅格系统。
.item-a { grid-column: 1 / 2; grid-row: 2 / 3; } .item-b { grid-column: 5 / 6; grid-row: 2 / 3; }
因此就可以用grid-auto-columns
和grid-auto-rows
来定义隐式创建的Grid Track的大小。私以为这种出格的事情,还是不要去做的好。
grid
grid
是在一个声明中设置所有以下属性的简写:grid-template-rows
, grid-template-columns
, grid-template-areas
, grid-auto-rows
, grid-auto-columns
, 和 grid-auto-flow
。
合起来写还是有些头大,我还是分开写(手动债见.jpg)
Grid Items Attributes
呼~终于把Grid Container的属性全写完了,下面再写Grid Item的。
grid-column-start / grid-column-end / grid-row-start / grid-row-end
上面有简单介绍过grid-column
和grid-row
,其实这四个属性就是这两种分开写的形式。因为合着写要方便很多,所以这里一笔带过,直接看语法。
.grid-item { grid-column-start: <number> | <name> | span <number> | span <name> | auto grid-column-end: <number> | <name> | span <number> | span <name> | auto grid-row-start: <number> | <name> | span <number> | span <name> | auto grid-row-end: <number> | <name> | span <number> | span <name> | auto }
grid-column/ grid-row
这里还是要单拎出来再次解释一下,因为有一些好玩的东西。
<div class="grid-wrapper"> <main class="grid-container"> <section class="grid-item grid-item-1">1</section> <section class="grid-item grid-item-2">2</section> <section class="grid-item grid-item-3">3</section> <section class="grid-item grid-item-4">4</section> <section class="grid-item grid-item-5">5</section> <section class="grid-item grid-item-6">6</section> <section class="grid-item grid-item-7">7</section> <section class="grid-item grid-item-8">8</section> <section class="grid-item grid-item-9">9</section> </main> </div> .grid-container { display: grid; grid-template-columns: repeat(3, 1fr); grid-template-rows: [row-1] 100px [row-2] 100px [row-3] 100px; .grid-item-1 { grid-column: 1 / span 2; grid-row: row-2 / 4; } }
先看grid-column
,span
就是该网格项将跨越所提供的网格轨道数量,所以这句等价于grid-column: 1 / 3;
再看grid-row
,我们在上面定义的第二条row的名称就叫做row-2
,因此row-2
就指代2
,所以这句等价于grid-row: 2 / 4;
所以这就是定义Grid Line
的名称的意义。
比起grid-template-areas
的写法,我觉得这个更加方便,因为在HTML中已经做了语义化,再用grid-template-areas
定义一遍语义不见得是最好的选择。
在线代码在下面:
grid-area
grid-area
为网格项提供一个名称,以便可以被使用网格容器grid-template-areas
属性创建的模板进行引用。 另外,这个属性可以用作grid-row-start
+ grid-column-start
+ grid-row-end
+ grid-column-end
的缩写。
这里就说一下缩写好了,看代码和图片。
.grid-item-d { grid-area: 1 / col4-start / last-line / 6 }
justify-self
justify-self
是沿着行轴线(row axis) 对齐网格项内的内容。
这又回到了和弹性布局类似的地方,照例写一下所有属性值:
-
start:将内容对齐到网格区域的左侧
-
end:将内容对齐到网格区域的右侧
-
center:将内容对齐到网格区域的中间(水平居中)
-
stretch:填充整个网格区域的宽度(这是默认值)
align-self
align-self
是沿着列轴线(column axis) 对齐网格项内的内容。
-
start:将内容对齐到网格区域的顶部
-
end:将内容对齐到网格区域的底部
-
center:将内容对齐到网格区域的中间(垂直居中)
-
stretch:填充整个网格区域的高度(这是默认值)
References
注:文章很多图片来自CSS Grid 布局完全指南(图解 Grid 详细教程),如有侵权,将于联系后删除。
Summarize
终于写完了,个人觉得Grid布局更像是一种全局性质的布局方案,一些小的部件如果用的话反倒可能会像Table系的布局一样被限制住。
当然优点显而易见,比如让grid-template-columns
和grid-template-rows
使用百分比或视口单位,做响应式开发可谓是方便了太多。
以上、よろしく。
NEXT POST
你可能不知道的 CSS3 Animation