[ 基础系列 ] - CSS 小测 04

241 阅读9分钟

系列文章

说在前面

本篇是张鑫旭老师的 CSS基础测试5 的阅后笔记。(感谢 33 同学的解答,使用 radio 的思路非常清奇。)

题目

先来看看题目。

完成下图所示的布局效果,包括选中状态,数量不固定,兼容移动端即可:

img-00

需求:

  • 自适应
  • 小块永远是正方形
  • 代码友好

思路

想要实现这样的网格效果,不考虑兼容性的话,其实最好的办法是 grid,即栅格系统,索性这里就简单介绍一下。

栅格系统

简介

所谓栅格系统,就是把一个块分成一定的份数,然后将目标内容放到指定的某一小块上。

举个例子,小明买了一套新房,打算置办些家具,如果事先没有任何规划,可能东放一张床,西放一个电视柜,南放一架钢琴......最终导致房间杂乱不堪,甚至可能某些家具塞不进去,而栅格系统则类比把房间事先分成一个个小块的区域:

img-01

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

效果如图:

img-02

这样,整个房间的布局一目了然,想要改变布局也很轻松合理。

创建栅格系统

首先,同 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;
}

效果如下:

img-03

上述代码和栅格系统相关的关键点如下:

  1. 通过 display: grid 创建了一个栅格系统
  2. 通过 grid-template-rows: 100px 100px 100px 定义了行高三个长度为 100px 的区域
  3. 通过 grid-template-columns: 100px 100px 100px 定义了列宽三个长度为 100px 的区域

如果没有定义定义列宽,则默认为 100%,而如果没有定义行高,则行高根据定义的列宽来进行计算,如果二者皆未定义,则行高为元素本身高度,列宽为 100%

这里只定义列宽为 grid-template-columns: 100px 100px 100px,效果如下:

img-04

可以看到,根上面的效果是一样的。

当然,这样的定义过于僵硬和复杂,如果要定义一个 100*100 的栅格系统,岂不是头晕眼花?所以对于这种重复的工作,自然会有对应的函数:

上面的代码,完全可以改成这样:

{
    grid-template-rows: repeat(3, 100px);
    grid-template-columns: repeat(3, 100px);
}

又因为前面提到的特性,grid-template-rows 的定义也可以删掉,那最终代码就是这样:

{
    grid-template-columns: repeat(3, 100px);
}

效果如下:

img-05

这样只是用一行语句就定义了 9 格单元格。

到这里有的同学可能会觉得,单元格的大小只能固定为 xxxpx,过于死板,所以 grid 也很贴心的为我们提供了一个参数:fr,这个参数表示单元格行高或者列宽所占比例,类似于 flex 中的 flex: 1

{
    grid-template-columns: repeat(3, 1fr);
}

上述代码就表示将列分成 3 份,每一份都占比 1/3,效果如下:

img-06

既然可以指定份数等比分割,那自然也可以指定大小自动分割:auto-fill 就可以完成这个需求:

{
    grid-template-columns: repeat(autofill, 100px);
}

效果如下:

img-07

另外, repeat 也可以同时绘制不同的单元格:

{
    grid-template-rows: repeat(2, 50px 100px);
    grid-template-columns: repeat(3, 100px);
}

先忽略列,这一段的执行结果是先绘制一行行高为 50px 的单元格,再绘制一行行高为 100px 的单元格,再把这个过程重复一次(总计两次),实际效果如下:

img-08

可见,与我们预期相符。

当然,绘制还有很多可以讲的,不过这里只是简单介绍一下,就不再赘述。

前面提到了,我们的布局分为两步:

  1. 创建栅格系统,绘制单元格
  2. 将目标内容放进对应的位置

放置内容

将内容放到栅格系统中对应的位置,有两种方案:

  • 根据栅格线放置内容
  • 根据栅格区域放置内容
根据栅格线放置内容

首先来看看下面栅格线是如何编号的:

img-09

很明显:

  • row:从左往右依次递增
  • column:从上往下依次递增

那么回到一开始创建的九宫格栅格系统,现在想将内容放到正中间:

.grid-layout div:first-child {
    grid-row-start: 2;
    grid-column-start: 2;
    grid-row-end: 3;
    grid-column-end: 3;
}

效果如下:

img-10

另外,我们可以对栅格线进行命名,这样方便大型布局的时候思路更加清晰:

.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;
}

效果如下:

img-11

css 很多属性都有简写形式,栅格元素的定位当然也不例外,还是上面那个效果,用简写形式则如下:

.grid-layout div:first-child {
    grid-row: r-start 2 / r-end 2;
    grid-column: c-start 2 / c-end 2;
  }

效果如下:

img-12

基础就是这样,更多的就不再赘述,接下来大致了解一下根据区域摆放内容

根据栅格区域放置内容

先来看看最基础的区域定位,需求同上,将元素放在九宫格的中央:

.grid-layout div:first-child {
    grid-area: 2/2/3/3;
  }

效果如下:

img-13

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

假设现在需要实现一个如下的三段式布局:

img-14

这个场景,使用区域布局就很方便:

首先我们创建一个栅格系统,划分区域:

.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";
  }

效果如下:

img-15

可以看到,这里已经成功画好了布局,现在我们需要将 headernavbarmainfooter 放入对应位置,只要利用区域定位的命名特性,就可以很简单的实现效果了。

<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;
  }

效果如下:

img-16

好了,大致介绍就到这里,简单讲讲说了这么多都已经有些偏题了,现在回到正题,要实现题中的布局效果,使用 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;
}

就这么简短几行代码,就实现了自适应的网格布局,效果如下:

img-17

选中效果

现在来实现选中效果,正常来说,第一反应是结合 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;
  }

效果如下:

img-18

最后在实现题目中的选中边框效果,这里使用 box-shadow 即可:

input:checked + .cb-item {
    box-shadow: 0 0 0 3px black;
}

效果如下:

img-19

结束语

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

然后这里是 在线 demo