有条件地对网格容器中的选定元素进行造型的方法

80 阅读7分钟

日历、购物车、画廊、文件探索器和在线图书馆是一些可选择项目以网格(即方形格子)显示的情况。你知道,即使是那些要求你选择所有带十字路口的图片或其他的安全检查。

🧐

我发现了一种在网格中显示可选择的选项的整洁方法。不,不是重现那个reCAPTCHA,而是简单地能够选择多个项目。而当两个或多个相邻的项目被选中时,我们可以使用聪明的:nth-of-type 组合器、伪元素和:checked 伪类,以一种它们看起来被分组的方式来样式它们。

在我们开始之前...

注意到一些事情对你来说是很有用的。例如,为了简单起见,我在我的演示中使用了静态HTML和CSS。根据你的应用,你可能需要动态地生成网格和其中的项目。为了专注于效果,我没有对可访问性进行实际检查,但在生产环境中,你肯定要考虑这类事情。

此外,我还使用了CSS Grid进行布局。我推荐同样的做法,但当然,这只是个人偏好,你的里程可能会有所不同。对我来说,使用网格可以让我轻松地使用兄弟姐妹选择器来针对一个项目的::before::after 伪。

因此,无论你想在你的应用程序中使用什么布局标准,都要确保这些假体仍然可以在CSS中被定位,并确保布局在不同的浏览器和屏幕上保持不变。

我们现在开始吧

正如你在前面的演示中所注意到的,检查和取消一个复选框元素会修改盒子的设计,这取决于它周围其他复选框的选择状态。这是可能的,因为我使用其相邻元素的伪元素而不是自己的元素来设计每个盒子的样式。

下图显示了每一列(第一列除外)中的盒子的::before 伪元素是如何与它们左边的盒子重叠的,以及每一行(第一行除外)中的盒子的::after 伪元素是如何与上面的盒子重叠的。

Two grids of checkboxes showing the placement of before and after pseudos.

下面是基本代码

标记是非常直接的。

<main>
  <input type=checkbox> 
  <input type=checkbox> 
  <input type=checkbox>
  <!-- more boxes -->
</main>

在最初的CSS中还有一些事情要做。但是,首先,网格本身。

/* The grid */
main {
  display: grid;
  grid:  repeat(5, 60px) / repeat(4, 85px);
  align-items: center;
  justify-items: center;
  margin: 0;
}

那是一个包含复选框的五行四列的网格。我决定抹去复选框的默认外观,然后给它们加上我自己的浅灰色背景和超级圆滑的边框。

/* all checkboxes */
input {
  -webkit-appearance: none;
  appearance: none;
  background: #ddd;
  border-radius: 20px;
  cursor: pointer;
  display: grid;
  height: 40px;
  width: 60px;
  margin: 0;
}

请注意,复选框本身也是网格。这是放置它们的::before::after 伪元素的关键。说到这一点,我们现在就来做。

/* pseudo-elements except for the first column and first row */
input:not(:nth-of-type(4n+1))::before,
input:nth-of-type(n+5)::after {
  content: '';        
  border-radius: 20px;
  grid-area: 1 / 1;
  pointer-events: none;
}

我们只选择不在网格第一列或第一行的复选框的伪元素。input:not(:nth-of-type(4n+1)) 从第一个复选框开始,然后选择从那里开始的每四个项目的::before 。但注意我们说的是:not() ,所以实际上我们所做的是跳过每四个复选框的::before 伪元素,从第一个开始。然后,我们对从第五个开始的每个复选框的::after 伪元素应用样式。

现在我们可以为不在网格第一列或第一行的每个复选框的::before::after 伪元素设置样式,使它们分别向左或向上移动,默认隐藏它们。

/* pseudo-elements other than the first column */
input:not(:nth-of-type(4n+1))::before { 
  transform: translatex(-85px);
}

/* pseudo-elements other than the first row */
input:nth-of-type(n+5)::after {
 transform: translatey(-60px); 
}

:checked 状态进行造型

现在,当复选框处于:checked 状态时,就要对它们进行造型。首先,让我们给它们一个颜色,比如说limegreen 背景。

input:checked { background: limegreen; }

一个复选框应该能够对其相邻的所有复选框进行重新设计。换句话说,如果我们选择网格中的第11个复选框,我们也应该能够给它周围的顶部、底部、左侧和右侧的复选框设置样式。

A four-by-five grid of squares numbered one through 20. 11 is selected and 7, 10, 12, and 15 are highlighted.

这是通过瞄准正确的伪元素来实现的。我们如何做到这一点?嗯,这取决于网格中的实际列数。如果在5⨉4的网格中,有两个相邻的盒子被选中,那么这里的CSS就是这样。

/* a checked box's right borders (if the element to its right is checked) */
input:not(:nth-of-type(4n)):checked + input:checked::before { 
  border-top-right-radius: 0; 
  border-bottom-right-radius: 0; 
  background: limegreen;
}
/* a checked box's bottom borders (if the element below is checked) */
input:nth-last-of-type(n+5):checked + * + * + * + input:checked::after {
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
  background: limegreen;
}
/* a checked box's adjacent (right side) checked box's left borders */
input:not(:nth-of-type(4n)):checked + input:checked + input::before {         
  border-top-left-radius: 0; 
  border-bottom-left-radius: 0; 
  background: limegreen;
}
/* a checked box's adjacent (below) checked box's top borders */
input:not(:nth-of-type(4n)):checked + * + * + * +  input:checked + input::before { 
  border-top-left-radius: 0; 
  border-top-right-radius: 0; 
  background: limegreen;
}

如果你愿意,你可以动态地生成上述代码。然而,一个典型的网格,比如说一个图片库,列的数量会很小,而且很可能是一个固定的项目,而行的数量可能会不断增加。特别是如果为移动屏幕设计的话。这就是为什么这种方法仍然是一种有效的方式。如果由于某种原因,你的应用程序恰好有有限的行和不断扩大的列,那么可以考虑将网格侧向旋转,因为在项目流中,CSS Grid会从左到右、从上到下(即逐行)排列。

我们还需要为网格中的最后一个复选框添加样式--它们没有全部被伪元素覆盖,因为它们是每个轴中的最后一个项目。

/* a checked box's (in last column) left borders */
input:nth-of-type(4n-1):checked + input:checked {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
/* a checked box's (in last column) adjacent (below) checked box's top borders */
input:nth-of-type(4n):checked + * + * + * + input:checked {
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

这些是一些棘手的选择器!第一个...

input:nth-of-type(4n-1):checked + input:checked

...基本上是这样说的。

在倒数第二列中,一个选中的<input> 元素旁边有一个选中的<input>

而这个nth-of-type 是这样计算的。

4(0) - 1 = no match
4(1) - 1 = 3rd item
4(2) - 1 = 7th item
4(3) - 1 = 11th item
etc.

所以,我们从第三个复选框开始,从那里选择每一个第四个复选框。如果该序列中的一个复选框被选中,那么我们也会对相邻的复选框进行样式设计,如果它们也被选中。

而这一行。

input:nth-of-type(4n):checked + * + * + * + input:checked

是这样说的。

一个<input> 元素被选中,直接与一个元素相邻,而这个元素又直接与另一个元素相邻,而这个元素又直接与处于选中状态的<input> 元素相邻。

这意味着我们正在选择每四个被选中的复选框。如果该序列中的一个复选框被选中,那么我们就对该复选框的下第四个复选框进行样式设计,如果它也被选中的话。

将其投入使用

我们刚才看到的是设计背后的一般原则和逻辑。同样,它在你的应用程序中的用处将取决于网格设计。

我使用了圆形边框,但你可以尝试其他形状,甚至尝试背景效果。现在你知道这个公式是如何工作的,剩下的就完全取决于你的想象力了。