系列文章
说在前面
本篇是张鑫旭老师的 CSS基础测试5 的阅后笔记。(感谢 33 同学的解答,使用 radio 的思路非常清奇。)
题目
先来看看题目。
完成下图所示的布局效果,包括选中状态,数量不固定,兼容移动端即可:

需求:
- 自适应
- 小块永远是正方形
- 代码友好
思路
想要实现这样的网格效果,不考虑兼容性的话,其实最好的办法是 grid,即栅格系统,索性这里就简单介绍一下。
栅格系统
简介
所谓栅格系统,就是把一个块分成一定的份数,然后将目标内容放到指定的某一小块上。
举个例子,小明买了一套新房,打算置办些家具,如果事先没有任何规划,可能东放一张床,西放一个电视柜,南放一架钢琴......最终导致房间杂乱不堪,甚至可能某些家具塞不进去,而栅格系统则类比把房间事先分成一个个小块的区域:

这个时候,再根据区域合理的摆放家具,比如床放在左边,从第二格开始,竖直向下,占 3 格;钢琴放在正中央,横竖各占 2 格......
效果如图:

这样,整个房间的布局一目了然,想要改变布局也很轻松合理。
创建栅格系统
首先,同 flex,给
- 块元素(如
div)添加:display: grid - 内联元素(如
span)添加:display: inline-grid
即可创建一个栅格系统。
grid 和 inline-grid 区别在于,inline-grid 容器为 inline 特性,因此可以和图片文字一行显示;grid 容器保持块状特性,宽度默认 100%,不和内联元素一行显示。
使用
grid-template-rows定义格子行高grid-template-columns定义格子列宽
那么首先来看看最简单的定义:
.grid-layout {
width: 300px;
height: 300px;
box-sizing: border-box;
border: 1px solid gray;
display: grid;
grid-template-rows: 100px 100px 100px;
grid-template-columns: 100px 100px 100px;
}
.grid-layout div {
background: dodgerblue;
margin: 10px;
}
效果如下:

上述代码和栅格系统相关的关键点如下:
- 通过
display: grid创建了一个栅格系统 - 通过
grid-template-rows: 100px 100px 100px定义了行高三个长度为 100px 的区域 - 通过
grid-template-columns: 100px 100px 100px定义了列宽三个长度为 100px 的区域
如果没有定义定义列宽,则默认为 100%,而如果没有定义行高,则行高根据定义的列宽来进行计算,如果二者皆未定义,则行高为元素本身高度,列宽为 100%
这里只定义列宽为 grid-template-columns: 100px 100px 100px,效果如下:

可以看到,根上面的效果是一样的。
当然,这样的定义过于僵硬和复杂,如果要定义一个 100*100 的栅格系统,岂不是头晕眼花?所以对于这种重复的工作,自然会有对应的函数:
上面的代码,完全可以改成这样:
{
grid-template-rows: repeat(3, 100px);
grid-template-columns: repeat(3, 100px);
}
又因为前面提到的特性,grid-template-rows 的定义也可以删掉,那最终代码就是这样:
{
grid-template-columns: repeat(3, 100px);
}
效果如下:

这样只是用一行语句就定义了 9 格单元格。
到这里有的同学可能会觉得,单元格的大小只能固定为 xxxpx,过于死板,所以 grid 也很贴心的为我们提供了一个参数:fr,这个参数表示单元格行高或者列宽所占比例,类似于 flex 中的 flex: 1:
{
grid-template-columns: repeat(3, 1fr);
}
上述代码就表示将列分成 3 份,每一份都占比 1/3,效果如下:

既然可以指定份数等比分割,那自然也可以指定大小自动分割:auto-fill 就可以完成这个需求:
{
grid-template-columns: repeat(autofill, 100px);
}
效果如下:

另外, repeat 也可以同时绘制不同的单元格:
{
grid-template-rows: repeat(2, 50px 100px);
grid-template-columns: repeat(3, 100px);
}
先忽略列,这一段的执行结果是先绘制一行行高为 50px 的单元格,再绘制一行行高为 100px 的单元格,再把这个过程重复一次(总计两次),实际效果如下:

可见,与我们预期相符。
当然,绘制还有很多可以讲的,不过这里只是简单介绍一下,就不再赘述。
前面提到了,我们的布局分为两步:
- 创建栅格系统,绘制单元格
- 将目标内容放进对应的位置
放置内容
将内容放到栅格系统中对应的位置,有两种方案:
- 根据栅格线放置内容
- 根据栅格区域放置内容
根据栅格线放置内容
首先来看看下面栅格线是如何编号的:

很明显:
- row:从左往右依次递增
- column:从上往下依次递增
那么回到一开始创建的九宫格栅格系统,现在想将内容放到正中间:
.grid-layout div:first-child {
grid-row-start: 2;
grid-column-start: 2;
grid-row-end: 3;
grid-column-end: 3;
}
效果如下:

另外,我们可以对栅格线进行命名,这样方便大型布局的时候思路更加清晰:
.grid-layout{
grid-template-rows: repeat(3, [r-start] 1fr [r-end]);
grid-template-columns: repeat(3, [c-start] 1fr [c-end]);
}
这时想要实现居中摆放的效果则使用如下代码:
.grid-layout div:first-child {
grid-row-start: r-start 2;
grid-column-start: c-start 2;
grid-row-end: r-end 2;
grid-column-end: c-end 2;
}
效果如下:

css 很多属性都有简写形式,栅格元素的定位当然也不例外,还是上面那个效果,用简写形式则如下:
.grid-layout div:first-child {
grid-row: r-start 2 / r-end 2;
grid-column: c-start 2 / c-end 2;
}
效果如下:

基础就是这样,更多的就不再赘述,接下来大致了解一下根据区域摆放内容
根据栅格区域放置内容
先来看看最基础的区域定位,需求同上,将元素放在九宫格的中央:
.grid-layout div:first-child {
grid-area: 2/2/3/3;
}
效果如下:

可以看出来,grid-area 是根据设置起始边和结束边来规定区域的。这样乍看起来和根据栅格线放置内容没什么区别,但区域定位还有一个特性,让它在某些场景下可以非常便捷的使用:
假设现在需要实现一个如下的三段式布局:

这个场景,使用区域布局就很方便:
首先我们创建一个栅格系统,划分区域:
.grid-layout {
width: 100vw;
height: 100vh;
box-sizing: border-box;
display: grid;
grid-template-rows: 60px 1fr 60px;
grid-template-columns: 60px 1fr;
grid-template-areas:
"header header"
"nav main"
"footer footer";
}
效果如下:

可以看到,这里已经成功画好了布局,现在我们需要将 header,navbar,main,footer 放入对应位置,只要利用区域定位的命名特性,就可以很简单的实现效果了。
<article class="grid-layout">
<header>Header</header>
<nav class="sidebar">Nav</nav>
<main>Main</main>
<footer>Footer</footer>
</article>
.grid-layout header,
.grid-layout .sidebar,
.grid-layout main,
.grid-layout footer {
background: deepskyblue;
background-clip: content-box;
padding: 10px;
}
.grid-layout header {
grid-area: header;
}
.grid-layout .sidebar {
grid-area: sidebar;
}
.grid-layout main {
grid-area: main;
}
.grid-layout footer {
grid-area: footer;
}
效果如下:

好了,大致介绍就到这里,简单讲讲说了这么多都已经有些偏题了,现在回到正题,要实现题中的布局效果,使用 grid 就非常方便。
<ul class="cb-group">
<li class="cb-wrap"></li>
<li class="cb-wrap"></li>
<li class="cb-wrap"></li>
<li class="cb-wrap"></li>
<li class="cb-wrap"></li>
<li class="cb-wrap"></li>
<li class="cb-wrap"></li>
</ul>
html,
body {
margin: 0;
}
.cb-group {
margin: 0;
padding: 10px;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-auto-rows: calc((100vw - 20px) / 5 - 10px);
grid-gap: 10px;
}
.cb-group .cb-wrap {
list-style: none;
background-color: deepskyblue;
}
就这么简短几行代码,就实现了自适应的网格布局,效果如下:

选中效果
现在来实现选中效果,正常来说,第一反应是结合 JS,点击某个色块的时候,给它添加一个 active 或者 checked 的类或属性,以此来标识被选中的色块。
而 33 同学的思路则是通过 radio 来实现这个效果,这个想法相比使用 JS,具有以下优点:
- 不需要任何 JS 参与
- 语意很好,包括对于辅助设备而言(屏幕,键盘)
- 开发便捷
<ul class="cb-group">
<li class="cb-wrap">
<input id="cb-checkbox__1" type="radio" name="cb-checkbox" checked/>
<label for="cb-checkbox__1" class="cb-item"></label>
</li>
<li class="cb-wrap">
<input id="cb-checkbox__2" type="radio" name="cb-checkbox" />
<label for="cb-checkbox__2" class="cb-item"></label>
</li>
<li class="cb-wrap">
<input id="cb-checkbox__3" type="radio" name="cb-checkbox" />
<label for="cb-checkbox__3" class="cb-item"></label>
</li>
<li class="cb-wrap">
<input id="cb-checkbox__4" type="radio" name="cb-checkbox" />
<label for="cb-checkbox__4" class="cb-item"></label>
</li>
<li class="cb-wrap">
<input id="cb-checkbox__5" type="radio" name="cb-checkbox" />
<label for="cb-checkbox__5" class="cb-item"></label>
</li>
<li class="cb-wrap">
<input id="cb-checkbox__6" type="radio" name="cb-checkbox" />
<label for="cb-checkbox__6" class="cb-item"></label>
</li>
<li class="cb-wrap">
<input id="cb-checkbox__7" type="radio" name="cb-checkbox" />
<label for="cb-checkbox__7" class="cb-item"></label>
</li>
</ul>
.cb-group input {
display: none;
}
.cb-group .cb-item {
display: block;
width: 100%;
height: 100%;
background-color: #fff;
border: 1px solid gray;
}
input:checked + .cb-item {
background-color: deeppink;
}
效果如下:

最后在实现题目中的选中边框效果,这里使用 box-shadow 即可:
input:checked + .cb-item {
box-shadow: 0 0 0 3px black;
}
效果如下:

结束语
到这里基本实现了自适应的色卡效果,想要继续优化体验的话,可以使用 tabindex,这个属性能增强键盘使用,具体可以看看张老师的 HTML tabindex属性与web网页键盘无障碍访问。
然后这里是 在线 demo