CSS - 元素数量选择器

根据元素数量匹配样式,解决 向前选择重复元素 问题。

<封面摄于江苏·南京的先锋书店,打卡网红书店。>

举个栗子

场景一

今天在给 hexo-theme-zhaoo文章目录 (toc),有个需求:当目录不存在时,不显示标题。

文章目录

抽象出 DOM 结构:

<aside class="toc-wrap">
<h3 class="toc-title">文章目录:</h3>
<ol class="toc">...</ol>
</aside>

最直接的思路是匹配 .toc,若该元素不存在,则将它前面的 .toc-title 隐藏。但是 CSS 没有向前选择,因为这样回引起回流。

需要想个办法曲线救国,其实借助它们的公共父元素 .toc-wrap 即可解决,若 .toc-wrap 只有一个子元素则将它隐藏。如下:

.toc-wrap
& > :only-child
display none

🌰 很简单,却值得思考一下,CSS 真的不能 “向前选择” 嘛?

场景二

在构建 栅格化 布局的时候,早期的 BootStrap 使用了大量的重复代码,类似这样:

@media (min-width: 768px) {
.col-md-1 { flex: 0 0 8.33333%; max-width: 8.33333%; }
.col-md-2 { flex: 0 0 16.6667%; max-width: 16.6667%; }
.col-md-3 { flex: 0 0 25%; max-width: 25%; }
.col-md-4 { flex: 0 0 33.3333%; max-width: 33.3333%; }
.col-md-5 { flex: 0 0 41.6667%; max-width: 41.6667%; }
.col-md-6 { flex: 0 0 50%; max-width: 50%; }
.col-md-7 { flex: 0 0 58.3333%; max-width: 58.3333%; }
.col-md-8 { flex: 0 0 66.6667%; max-width: 66.6667%; }
.col-md-9 { flex: 0 0 75%; max-width: 75%; }
.col-md-10 { flex: 0 0 83.3333%; max-width: 83.3333%; }
.col-md-11 { flex: 0 0 91.6667%; max-width: 91.6667%; }
.col-md-12 { flex: 0 0 100%; max-width: 100%; }
}
@media (min-width: 992px) {
.col-lg-1 { flex: 0 0 8.33333%; max-width: 8.33333%; }
.col-lg-2 { flex: 0 0 16.6667%; max-width: 16.6667%; }
.col-lg-3 { flex: 0 0 25%; max-width: 25%; }
.col-lg-4 { flex: 0 0 33.3333%; max-width: 33.3333%; }
.col-lg-5 { flex: 0 0 41.6667%; max-width: 41.6667%; }
.col-lg-6 { flex: 0 0 50%; max-width: 50%; }
.col-lg-7 { flex: 0 0 58.3333%; max-width: 58.3333%; }
.col-lg-8 { flex: 0 0 66.6667%; max-width: 66.6667%; }
.col-lg-9 { flex: 0 0 75%; max-width: 75%; }
.col-lg-10 { flex: 0 0 83.3333%; max-width: 83.3333%; }
.col-lg-11 { flex: 0 0 91.6667%; max-width: 91.6667%; }
.col-lg-12 { flex: 0 0 100%; max-width: 100%; }
}

在使用 CSS 预处理器 后可以简化成下面这样,但是编译后依然存在大量重复代码。

for $i in 1 .. 12
.col-{$i}
col-attr round(($i * 100 / 12) %, 6)
@media (min-width 576px)
for $i in 1 .. 12
.col-sm-{$i}
col-attr round(($i * 100 / 12) %, 6)
@media (min-width 768px)
for $i in 1 .. 12
.col-md-{$i}
col-attr round(($i * 100 / 12) %, 6)
@media (min-width 992px)
for $i in 1 .. 12
.col-lg-{$i}
col-attr round(($i * 100 / 12) %, 6)
@media (min-width 1200px)
for $i in 1 .. 12
.col-xl-{$i}
col-attr round(($i * 100 / 12) %, 6)

灵魂拷问,CSS 真的不适合处理 “重复元素” 的场景嘛?

前置知识

一些 CSS3 选择器:

  • > 子元素选择器
  • + 相邻元素选择器
  • ~ 兄弟元素选择器

一些 CSS3 伪类:

  • :only-child 父元素的唯一子元素
  • :first-child 父元素的第一个子元素
  • :last-child 父元素的最后一个子元素
  • :nth-child(n) 父元素的第 N 个子元素
  • :nth-last-child(n) 父元素的倒数第 N 个子元素
  • :nth-child(xn+y) 父元素的第 xN + y 个子元素
  • :nth-last-child(xn+y) 父元素的倒数第 xN + y 个子元素

化学反应

结合以上的 选择器伪类 可以产生一些有趣的 “化学反应”,基于元素的数量来匹配样式。

DOM 结构如下:

<ul>
<li>1</li>
<li>2</li>
<li>3</li>
...
</ul>

André Luís

André Luís 方案如下:

/* 只有一个元素时 */
ul>li:nth-child(1):nth-last-child(1) { width: 100%; } /* 匹配第一个元素 */
ul>li:only-child { width: 100%; } /* 匹配第一个元素 */
/* 有且仅有两个元素时 */
ul>li:nth-child(1):nth-last-child(2), /* 匹配第一个元素 */
ul>li:nth-child(2):nth-last-child(1) { /* 匹配第二个元素 */
width: 50%; /* 合起来就是匹配所有元素 */
}
/* 有且仅有三个元素时 */
ul>li:nth-child(1):nth-last-child(3),
ul>li:nth-child(2):nth-last-child(2),
ul>li:nth-child(3):nth-last-child(1) {
width: 33.3333%;
}

Clever lists with CSS3 selectors

Lea Verou

上述方案存在一个问题,当元素数量过多时,还是存在大量的重复选择器。升级版的 Lea Verou 方案只需两行固定的选择器即可解决。

/* 只有一个元素时 */
ul>li:first-child:nth-last-child(1) {
width: 100%;
}
/* 有且仅有两个元素时 */
ul>li:first-child:nth-last-child(2),
ul>li:first-child:nth-last-child(2) ~ li {
width: 50%;
}
/* 有且仅有三个元素时 */
ul>li:first-child:nth-last-child(3), /* 匹配第一个元素 */
ul>li:first-child:nth-last-child(3) ~ li { /* 匹配除第一个元素外的所有元素 */
width: 33.3333%;
}

Styling elements based on sibling count

再改进

不确定元素数量时:

/* 大于三个元素时 */
ul>li:first-child:nth-last-child(n+3),
ul>li:first-child:nth-last-child(n+3) ~ li {
width: calc(100% / n);
}

总结

通过上述方法即可解决 向前选择重复元素 这两个问题,遇到实际场景时借助父元素灵活运用即可。

查看评论