根据条件对网格容器中的选定元素进行样式设置

Avatar of Preethi
Preethi

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 200 美元的免费积分!

日历、购物车、画廊、文件浏览器和在线库是一些在网格(即方形格点)中显示可选择项目的场景。您知道,即使是那些要求您选择所有带有斑马线或其他内容的图像的安全检查。

🧐

我发现了一种在网格中显示可选择选项的简洁方法。不,不是重新创建那个 reCAPTCHA,而只是能够选择多个项目。当两个或多个相邻项目被选中时,我们可以使用巧妙的 :nth-of-type 组合器、伪元素和 :checked 伪类以使它们看起来组合在一起的方式对它们进行样式设置。

组合器和伪元素来获得圆角复选框的想法来自我之前写的一篇 文章。这是一个简单的单列设计

但是,这一次,圆角效果应用于网格上垂直和水平轴上的元素。您不必阅读我关于复选框样式设置的上一篇文章,因为我将涵盖您需要了解的全部内容。但是,如果您有兴趣了解本文中我们正在做的内容的精简版本,那么那篇文章值得一看。

开始之前...

注意一些事情会很有用。例如,为了简单起见,我在演示中使用静态 HTML 和 CSS。根据您的应用程序,您可能需要动态生成网格及其中的项目。为了专注于效果,我省略了对可访问性的实际检查,但在生产环境中,您肯定要考虑这种事情。

另外,我使用 CSS 网格进行布局。我推荐这样做,当然,这只是一种个人偏好,您的效果可能会有所不同。对我来说,使用网格可以让轻松使用同级选择器来定位项目的 ::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 网格会将它们从左到右、从上到下排列(即逐行排列)。

我们还需要为网格中的最后一个复选框添加样式——它们并不都由伪元素覆盖,因为它们是每个轴的最后一个项目。

/* 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> 元素相邻。

这意味着我们选择的是每个被选中的第四个复选框。如果该序列中的复选框被选中,那么我们也会设置该复选框之后的第四个复选框的样式,如果它也被选中。

应用

我们刚才介绍的是设计背后的基本原理和逻辑。再说一次,它在您的应用程序中的用处将取决于网格设计。

我使用了圆角边框,但您可以尝试其他形状,甚至可以尝试背景效果(Temani 为您提供了一些想法)。现在您已经了解了公式的工作原理,剩下的就完全取决于您的想象力了。

以下是一个在简单日历中可能看起来的样子

再说一次,这仅仅是一个使用静态标记的粗略原型。而且,在日历功能中,需要考虑很多很多可访问性问题。


总结一下!相当不错,对吧?我的意思是,正在发生的事情并没有什么“新颖”之处。但这是一个很好的关于选择事物的 CSS 示例。如果我们掌握了更多使用组合器和伪元素的更高级的选择技术,那么我们的样式设置能力可以远远超出设置单个项目的样式——正如我们所看到的,我们可以根据另一个元素的状态有条件地设置项目的样式。