前端开发者学堂 - fedev.cn

使用CSS Mod Queries控制选择器范围

发布于 大漠

最近有一个项目需求,希望一个简单的列表在一个网格中完美显示,比如只有一个单元格的时候,列表占据整个容器宽度,但总是不太好控制。因此,有时当你忙着一件事情时,问你有没有办法可以实现,我试图使用可以想到的办法,总是会有空白出现,我就会心烦意乱的说,实现不了。

当时,我正在伦敦一家新闻机构做网站。花了一年多时间把网站从CMS迁移到Adobe AEM平台上,并且UI做了很大的改进。在这个阶段,我开始关注UI的构建以及其新特性。另外整个开发团队划分成了许多个小团队,而我和我的团队主导就是UI的构建。

每个中心页面实际上是一个列表,列表只是用来帮助读者能找到他们更感兴趣的内容。你可以想象,一个新闻网站几乎是由内容列表构成。页面只是垂直排列的话,对于他的读者而言是没有任何吸引力,但我们希望读者在浏览网站的时候能立马找到他们感兴趣的内容。如此一来,就需要对不同的列表划分单独的区域,并且让这些列表能更协调的向读者展示。简而言之,通过视觉的调整,让整个页面更好的显示其可用性和有效性。

我所说的简单列表是指主版面顶部总是高调的突出或强调一个特定的兴趣点。从一个项目开始,随着时间每天增加文章列表数。为了能更好的排列,需要一个网格列表,而不是一个单一的列,如此一来,有的时候总是会有单个列表排在最后面。而我们想要的需求是,不管有多少个子元素,它的布局必须保持整洁和干净。也就是说,不管有多少个列表项,在网格中都能很好的平整显示。第一项宽度是为100%,后面两个50%,另外后续所有列表项宽度是33%。这样一来,所谓的简单列表突然变得不再是那么简单。

不是每个人都想要一个通用的网格或者堆栈相同的列表项。如果你知道一个精确的列表长度,可能通过硬编码的方式构建一个优雅的网格布局,但如果长度是可变的话,那么布局变得就更具挑战性:当最后一排列表项不到三个时,怎么保持最后一个能整洁干净排列

当更多的列表项添加进来之后将会打破我们预期的布局,直接影响视觉效果

在实际操作过程中,发意识到了解列表的长度对于布局并没有起到很有效的帮助。一开始还以为能在@Heydon Pickering分享的《Quantity Queries for CSS》文章中找到相关的解决方案,但最终我以失败而告终。

Quantity Queries for CSS》译文可以点击这里阅读。

我的需求中,列表数量是可以任意长度的,这样一来,我就无法通过数量查询来解决。而且我也不可能预测到列表有多少个数量。加上,有的时候还有“加载更多”的按钮,当用户点击这个按钮之后,会动态加入另一个10个列表项。如此一来,我需要一个不同的解决方案。

崩溃之后的我,静下来问自己,假如@Lea Verou碰到这样的需求会怎么处理?好吧,不恐慌或许这是一个好的开始。此外,它将有助于简化和确定潜在的需求。因为列表从根本上是一行显示三列,这样我只需要知道列表除以3之后的余数是多少。

模查询(Mod Query)

能过通过数量和兄弟选择器来控制样式,这是一个强大的特性,但它有一个致命的缺陷,那就是无法对未知长度的列表做控制。在下面的示例中,通过整除一个数而不是知道列表的长度个数来控制自己的列表项。

不幸的是,在CSS中没有一个取模查询,但我们可以通过结合:nth-child(3n)(以名取模选择器)和:first-child两个选择器来模拟一个CSS取模查询。

比如下面的代码,选择一切能被3整除的列表:

/*选择列表中所有能被3整除的列表*/
li:nth-last-child(3n):first-child,
li:nth-last-child(3n):first-child ~ li { 
 	
}

@clanceyp通过这种取模查询的方式,能被3整除的列表项,图标都高亮显示了。效果如上图所示,具体示例如下:

我们一起来讨论一下代码。就拿上面的示例做为讨论的列子。

CSS选择器:

li:nth-last-child(3n):first-child ~ li

选择所有兄弟元素:

... ~ li

第一个子元素(在这个示例中,就是列表中的第一个li):

...:first-child ...

选择列表后面开始选择3的倍数的列表:

...:nth-last-child(3n):...

上面的组合选择器意味第一个子元素是列到倒数第3n个列表项和选择所有的兄弟元素。

如果查询第一个列表的所有兄弟元素,但不包括第一个列表项本身,这样就需要单独的为它添加一个选择器:

li:nth-last-child(3n):first-child,
li:nth-last-child(3n):first-child ~ li { 

}

来看个简单的示例:

点击“add list item”按钮,当列表数分别能被2345整除时,具有不同的样式风格。

余数呢

通过取模查询(Mod Query),如果列表项能被3整除的列表都能选中,但如果列表不能被3整除时,也就是有余数的时候,需要给列表一个不同的样式。(如果余数为1的时候,只需要从倒数第二个开始计数,而不是最后一个。如果一来只需要在查询中简单的+1就可以。)

li:nth-last-child(3n+1):first-child,
li:nth-last-child(3n+1):first-child ~ li { 
    … 列表数除以3的余数是1的列表 …  
}

同上,如果余数是2,只需要换成+2即可:

li:nth-last-child(3n+2):first-child,
li:nth-last-child(3n+2):first-child ~ li { 
    … 列表数除以3的余数是2的列表 …  
}

创建选择器范围

现在有方法确定列表长度能否被任何给定的数字整除,有没有余数,但很多时候仍然需要一个选择范围。和取模查询一样,原始的CSS选择器是没有这样的,但同样的,可以结合:nth-child(n)(一切大于n)和:nth-child(-n)(一切小于n)两个选择器实现。

假设要选择列表中的第35这几个列表项,可以这样使用:

li:nth-child(n+3):nth-child(-n+5){ 
    
}

事实上,可以使用:nth-child(n)选择器和具体的列表项目数实现上面的的效果,也就是直接使用:

li:nth-child(3),
li:nth-child(4),
li:nth-child(5) {
    ...
}

但是定义一个开始和结束来确定是一个选择器范围,这功能更强大。接下来,我们来看看这样能做些什么?

从第五个列表项向前选择,包括第五个列表项:

li:nth-child(-n+5){ … }

从第三个列表项向后选择,包括第三个列表项:

li:nth-child(n+3){ … }

两个选择器组合在一起,就创建了一个选择器范围:

li:nth-child(n+3):nth-child(-n+5){ … }

来看一个示例,所有的列表都放在一个网格中,每个列表都包括了图片、标题和描述。第一行只需要显示图片(标题和描述都隐藏),第二行和第三行,显示图片、标题和描述,而从第四行开始隐藏图片,而且标题和描述显示在一行。

具体的可以查看@clanceyp写的一个示例:

示例还做了响应式处理,缩小你的视窗,可以看到不同的效果,但始终保持网格第一行只显示缩略图,第二行和第三行显示缩略图、标题和描述,第四行开始隐藏缩略图,而且标题和描述都在同一行显示。

通过范围选择器,第一个选择器选择前三个,第二个选择器是从第4个开始到第9个结束,选择49的一个范围,第三个选择器是从第10个选择器开始向后选择。正如上述,使用了CSS媒体查询在不同的断点改变选择器的范围,这样可以让我们网格布局保持一个不错的效果。

SCSS Mixin

我们一直都在使用CSS处理器,可以通过使用预处理函数来简化我们的代码。下面的代码就是通过SCSS创建了选择器范围和取模查询范围的混合宏:

// 选择器范围的混合宏
@mixin select-range($start, $end){
  &:nth-child(n+#{$start}):nth-child(-n+#{$end}){
   @content;
   }
}

// 取模查询的混合宏
@mixin mod-list($mod, $remainder){
  &:nth-last-child(#{$mod}n+#{$remainder}):first-child,
  &:nth-last-child(#{$mod}n+#{$remainder}):first-child ~ li {
    @content;
    }
}

实际使用的时候,可以像下面的代码一样在li的嵌套中调用声明好的混合宏:

li {
    @include mod-list(3, 0){
  		@include select-range(3, 5){
    		// styles for items 3 to 5 in a list mod 3 remainder = 0
    	}
  	}
}

有关于SCSS更多实用的混合宏,可以查询SassMagic,里面提供了常用的SCSS混合宏。如果你有类似常用的混合宏,欢迎给SassMagic提交PR。

有效组织在一起

现在我有一个小的工具来帮助我处理取模、范围或者范围中取模,更好的脱离固定长度或固定的列表布局。创造性的得用取模查询和范围选择器让我们应用样式改变元素的布局。

回到最初的原始需求,让列表的表现行为变得很明显。如果假设列表样式是三的倍数,那么只会有两个其他情况:

  • 列表数被三整除,余数是一
  • 列表数被三整除,余数是二

如果有一个剩余的列表项,我会让第二行显示三个列表项,而不是默认的两个列表项,但如果剩余的列表项是两个,可以让第三行显示两个列表项,第四和第五个列表项宽度为50%

最后,我不需要大量的查询,这让我实际操作起来变得相当的简单。但有一个特殊情况:如果列表只有两个列表项呢? 解决办法就是查询第二个列表项,而且同时其也是最后一个列表项:

li:nth-child(2):last-child { 

}

查询最终并不像我预期想的那么困难,只需要把取模和范围选择器组合在一起。

li:nth-last-child(3n):first-child /* mod query */ 
~ li:nth-child(n+3):nth-child(-n+5){ /* range selector */
    /*给第三到第五个元素设置样式,而且列表能被三整除*/
}

总之,针对文章开头提到的需求,我们的CSS代码如下所示:

/*  默认列表项能被3整除,余数为0,
    每个列表项的宽度为33%,
    第一个列表项宽度为100%,
    第二个列表项和第三个列表项宽度为50%
*/
li {
  width: 33.33%;
}
li:first-child {
  width: 100%;
}
/* 选择器范围,第2至第3列表项 */
li:nth-child(n+2):nth-child(-n+3){
  width: 50%;
}
/* 覆盖前面的样式 */
/* 列表项被3整除时,余数为1 */
li:nth-last-child(3n+1):first-child ~ li:nth-child(n+2):nth-child(-n+3) {
  width: 33.33%; /* 覆盖第2和第3个列表项宽度*/
}
/* 列表项被3整除时,余数为2 */
li:nth-last-child(3n+2):first-child ~ li:nth-child(n+4):nth-child(-n+5) {
  width: 50%; /* 覆盖第4和第5个列表项宽度 */
}
/* 特殊情况,列表项只有两个的时候 */
li:nth-child(2):last-child {
  margin-left: 25%;
}

经验之谈,注意浏览器的支持范围

取模查询和范围选择器依赖于CSS3选择器,所以支持CSS3的现代浏览器都支持,包括IE9+。

我创建了一个取模或范围选择器的工具,你可以直接使用这个工具来实现相同的功能。

当我第一次遇到QQs时,从理论中说我觉得他是强大而又有趣的,但并没有实战过。然而随着移动应用超过桌面运用以及响应式设计规范需要列表在不同的断点下列表项宽度不一样变得更为常见。我发现这样的特性比以往任何时候都显得更为重要,为了方便UI开发人员更好的使用这些特性,UI开发人员开发类似的工具也变成日常工作的一部分。

扩展阅读

本文根据@Patrick Clancey的《Using CSS Mod Queries with Range Selectors》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:http://alistapart.com/article/using-css-mod-queries-with-range-selectors

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:https://www.fedev.cn/css3/using-css-mod-queries-with-range-selectors.htmlAir Force 1 Foamposite