CSS秘密花园: 相邻元素样式

发布于 大漠

CSS Secrets》是@Lea Verou最新著作,这本书讲解了有关于CSS中一些小秘密。是一本CSSer值得一读的一本书,经过一段时间的阅读,我、@南北@彦子一起将在W3cplus发布一系列相关的读后感,与大家一起分享。

CSS Secrets

问题

在很多情况下,我们需要给元素的兄弟元素以不同的样式风格呈现。主要的用例是提高用户体验和在大屏幕中不断增加列表荐。下面是一些使用场景:

邮件列表或类似基于文的列表展示。如果我们只有为数不多的列表项,我们可以单独展示。随着列表项的增加,我们可以减少列表的预览项。当列表的高度大于视窗高时,我们可能会选择隐藏一些列表项,或者在尾添加一个查看更多的按钮,让用户在不滚动滚动条也能浏览列表。

App中的待办事项列表(to do list),可以给几个列表项设置大的文本样式,但随着列表项数目增加,列表项目的设置较小的字号。

用来显示颜色的调色板APP。有人可能想随着颜色数量增加,利用好空间让颜色控制面板变得更紧凑些,如下图所示:

相邻元素样式

随着颜色的数量增加,逐步缩小控制颜色的埠和减少可用空间。注意,在特殊情况之下,当只有一个颜色时,会隐藏删除按钮。

具有多个<textarea>的APP,我们每次新增加一个文本域,他就比前一个更小一些(例如bytesizematters.com)。

然而,针对元素相邻兄弟元素来选择目标元素不是简单的CSS选择器。例如,当列表总数是4个时给列表不同样式。我们可以使用:nth-child(4)来选择列表的第四个列表项,但这并不是我们所需要的,我们要的是当列表总数为4的时候,选择列表的每一个列表项。

有一个想法就是使用通用选择符(~)和:nth-child()选择器配合使用,例如li:nth-child(4),li:nth-child(4)~li。不管列表项总数有多少,只选择了第四个项和其之后的所有列表项,如下图所示:

相邻元素样式

因为没有“向后选择器”和选择元素之前的兄弟元素的选择器,那么CSS就注定不能选择到需要的目标元素吗?我们不应该失去信心,一切皆有可能。

解决方案

有一个特殊案例就是只有一个单独的列项项目,有一个明显的解决方案::only-child选择器,这个选择器就是为这种情况而生的。他是非常有用的,这也是为什么会将其添加到规范中。例如前面提到的颜色调控板APP,当只有一个颜色时,会隐藏删除按钮,那么就可以使用:only-child选择器来实现。

li:only-child {
    /* 当只有一个列表项时的样式 */
}

我们在这一节讨论的是:nth-child()选择器,但这一切的讨论都同样适用于:nth-of-type() 选择器。往往这种类型选择器更适用,通常情况之下,容器中会有不同类型的兄弟元素,使用这种类型选择器,我们只要关心一种类型。这里将使用列表元素做为示例讨论,但讨论中的一切都适用于任何类型元素。

然而,:only-child相当于:first-child:last-child,他是第一项也是最后一项,从逻辑上可以得出结论,他就是唯一的项目。其实,:last-child就是:nth-last-child(1)简写。

li:first-child:nth-last-child(1) {
    /* 和li:only-child等同 */
}

当然,现在1就是一个参数,我们可以根据自己的喜好调整这个参数。你能猜出li:first-child:nth-last-child(4)选到的目标是什么?如果你回答,当列表的总数是四个的时候,选择到:only-child的目标元素,你可能太过于乐观了一点。其实事实并不是这样,但是离我们正确的选择目标越来越接近。分别考虑两个选择器::first-child:nth-last-child(4)。因此,在相同时间从元素第一个子元素开始计数和倒数第四个子元素计数。那么哪些元素满足这个范围呢?

正确答案是,只个四个元素的列表的第一个元素被选中,如下图所示:

相邻元素样式

这并不是我们想要的结果,但已非常接近我们需要的效果。因为我们在一个列表中选择到了第一个子元素,我们可以使用通用选择符(~)来选择第一个子元素后的相邻兄弟元素。这样一来,如果列表只有四个列表项时,所有列表项都将被选中。这正是我们试图要实现的效果:

li:first-child:nth-last-child(4),
li:first-child:nth-last-child(4) ~ li {
    /* 目标选择了仅有四个列表项的列表所有列表项 */
}

为了避免代码的冗长和重复的解决方案,可以使用预处理器,例如SCSS。尽管现有的预处理器语法比图笨拙:

/* 定义mixin */
@mixin n-items($n) {
    &:first-child:nth-last-child(#{$n}),
    &:first-child:nth-last-child(#{$n}) ~ & {
@content; }
}
/* 像这样使用 */
li {
    @include n-items(4) {
        /* 这里写样式 */
    }
}

选择兄弟元素范围

在大多数实际运用中,我们并不希望瞄准特定数量的列表,而是希望在一个范围内。有一个小技巧,我们可以使用:nth-child()选择器来指定一个选择范围。例如选择第四个之后的列表项。除了使用简单的数字作为参数之外,还可以使用an+b表达式作为参数(例如::nth-child(2n+1)),其中n是一个变量,范围是从0+∞(实际上,值在某点不会选择任何东西,因为我们元素的数量是有限的)。如果我们使用一个表达式n+b(a默认下是1),那么,如果n不是一个正整数时可以给我们一个小于b值的范围。因此,表达式n+b可以用来第b个值为第一个子元素所有子元素。例如::nth-child(n+4)选择除了第一、第二、第三的所有子元素。如下图所示:

相邻元素样式

如果你不想为:nth-*选择器做过多的思考,你可以使用一个测试工具,我写了一个在线的工具,足够你做测试。

我们可以利用这个功能来选择列表项是四个或更多的列表子元素,如下图所示:

相邻元素样式

在这种情况下,我们可以在:nth-last-child()使用n+4这个表达式:

li:first-child:nth-last-child(n+4),
li:first-child:nth-last-child(n+4) ~ li {
    /* 目标列表选择最少包含四个列表项 */
}

同样,表达式-n+b可以用来选择第b个元素。因此,选择仅有四个或更少的列表项的列表的所有子元素时,就可以使用这种表达式。

相邻元素样式

如上图所示,我们代码可以这样写:

li:first-child:nth-last-child(-n+4),
li:first-child:nth-last-child(-n+4) ~ li {
    /* 选择列表项最多只有四个的列表所有子元素 */
}

当然,我们可以结合这两个,但现在的代码变得更加笨拙。假设我们列表包含2~6项的列表所有子元素:

li:first-child:nth-last-child(n+2):nth-last-child(-n+6),
li:first-child:nth-last-child(n+2):nth-last-child(-n+6) ~ li{
    /*选择包含2-6个列表项的列表所有子元素*/
}

回过头来看本节最前面提出的案例:颜色面板APP效果:

如需转载,烦请注明出处:https://www.fedev.cn/css3/css-secrets/styling-by-sibling-count.htmljordans for sale custom