返回 登录
0

CSS Selectors Level 4新特性全面解析

阅读10123

作者:李滨泓,曾实习于饿了么大前端部,爱开源,爱技术,爱Google的所有东西。
本文为《程序员》原创文章,未经允许不得转载,更多精彩文章请订阅2017年《程序员》
责编:陈秋歌,关注前端开发领域,寻求报道或者投稿请发邮件chenqg#csdn.net。欢迎加入“CSDN前端开发者”微信群,参与热点、难点技术交流。请加群主微信「Rachel_qg」,申请入群,务必注明「公司+职位」。另可申请加入CSDN前端开发QQ群:465281214。

前言

当看到 CSS Selectors Level 4 很多人会理所当然地喊出 CSS4。但是,这里必须明确一个概念,目前所谓的 CSS3 和 CSS4 都是 CSS2.1 以后对某一些 CSS 模块进行升级更新后的称呼。CSS3 和 CSS4 永远都不会出现,它们只是为了区分 CSS 模块升级后的等级,例如有些 CSS 选择器在之前就存在了,但是此时我们为它添加了新的特性,那么这个模块就升级到了 CSS Selectors Level 3 中,如果再一次升级,那么就变成了 CSS Selectors Level 4。下面我们可以对目前 W3C 工作草案中的 CSS Selectors Level 4 新特性进行一个阶段性总结,之所以叫阶段性,是因为未来可能有更多的新特性加入。

Negation pseudo-class——:not()

:not() 用于将符合规则的元素剔除,将样式规则应用于其他元素上。在 CSS3 中已经有 :not(),不过在 CSS3 中只能使用简单的匹配规则,例如 :not(p) 用来选择不是 <p></p> 的元素。而在 CSS4 中,可以应用更复杂的匹配规则,但是同样地不允许嵌套使用,例如 :not(:not(...))

.negation {
  color: black;
}

.negation .default:not([data-red="no"]) {
  color: red;
}

.negation .default a {
  color: green;
}

.negation .default a:not([rel="green"], [rel="default"]) {
  color: blue;
}
<div class="negation">
  <div class="default" data-red="no">
    <a href="http://www.baidu.com" rel="green">这里是绿色</a>
    <a href="http://www.ele.me" rel="default">这里也是绿色</a>
    <a href="http://www.sina.com" rel="blue">这里是蓝色</a>
  </div>
  <div class="default" data-red="no">
    这里是黑色
  </div>
  <div class="default" data-red="yes">
    这里是红色
  </div>
</div>

:not() 小提示

我们可以利用 :not() 来对 CSS 样式进行一个优先级提升,例如 div:not(span) {…}div {…} 是同个概念,但是明显地前者的优先级更高。

想解锁更多 :not() 的使用姿势就去看 The Negation Pseudo-class 草案

Matches-any Pseudo-class——:matches 伪类

:matches() 用于匹配所述规则的元素,并应用相应的样式规则,同样不允许嵌套使用,-webkit-any()-moz-any() 是它的两个兼容性写法。它可以让我们节省书写大量的 CSS 样式匹配规则,让我们从大量重复的规则书写中解放出来。

.matches {
  color: black;
}

.matches :matches(span, div) :matches(span, div) {
  color: green;
}
/*
等同于
.matches span div,
.matches span span,
.matches div span,
.matches div div {
  color: green;
}
 */

.matches :-webkit-any(span, div) :-webkit-any(span, div) {
  color: green;
}

.matches :-moz-any(span, div) :-moz-any(span, div) {
  color: green;
}

.matches :matches(.a, .b) :matches(.a, .b) {
  color: red;
}
/*
等同于
.matches .a .a,
.matches .a .b,
.matches .b .a,
.matches .b .b {
  color: red;
}
 */

.matches :-webkit-any(.a, .b) :-webkit-any(.a, .b) {
  color: red;
}

.matches :-moz-any(.a, .b) :-moz-any(.a, .b) {
  color: red;
}
<div class="matches">
  <span>
    <div>绿色</div>
  </span>
  <span>
    <span>绿色</span>
  </span>
  <div>
    <span>绿色</span>
  </div>
  <div>
    <div>绿色</div>
  </div>
  <div class="a">
    <div class="b">红色</div>
  </div>
  <div class="b">
    <div class="a">红色</div>
  </div>
    <div class="a">
    <div class="a">红色</div>
  </div>
  <div class="b">
    <div class="b">红色</div>
  </div>
</div>

Case-Sensitivity——不区分大小写匹配标识

Case-Sensitivity 用于声明某个匹配规则中,对字符串或者某个 value 的匹配不区分大小写。该标志声明于 ] 即右中括号之前,例如 [data-value="case" i],其中的 i 就是 Case-Sensitivity 标识。但是如果我们需要明确区分大小写区别的时候,该标识可能会导致某些不可意料的后果,所以使不使用该标识应该明确使用的场景是否对数据来源的大小写敏感。

.case-sensitivity :matches([data-value="case" i]) {
  color: yellow;
}
<div class="case-sensitivity">
  <p data-value='Case'>Case</p>
  <p data-value="case">case</p>
</div>

以上的例子,data-value 虽然既有大写也有小写,但是由于我们声明了 Case-Sensitivity,所以无论大小写都会被匹配。像例子中 caseCaseCASE 等都会被匹配。

The Directionality Pseudo-class——:dir()

:dir() 用于匹配符合某个方向性的元素,例如 :dir(ltr)dir(rtl)。顾名思义,ltr 表示 left to right,即方向从左到右,rtl 表示 right-to-left,即方向从右到左。值得注意的是,使用 :dir() 匹配元素和使用 [dir=...] 在某个程度上是一样的效果,但是一个区别是 [dir=...] 无法匹配到没有显示声明 dir 的元素,但是 :dir() 却可以匹配到由浏览器计算得到或者继承来的 dir 属性的元素,详情可以看一下草案。因此,如果我们有明确地对某个元素声明 dir,那我们大可以使用 [dir=...] 的形式来匹配某个元素,但是如果我们只是单纯从父元素继承而来的 dir,那么此时还是需要用到 :dir()

.dir :dir(ltr) {
  color: blue;
}
.dir :dir(rtl) {
  color: green;
}
<div class="dir">
  <p dir="ltr">从左到右</p>
  <p dir="rtl">从右到左</p>
</div>

The Language Pseudo-class——:lang()

:lang() 用于匹配声明了 lang=value 的元素,并且可以使用通配符匹配,例如 p:lang(*-CH) 将可以匹配 de-CHp 元素。

.lang p:lang(de-DE) {
  color: green;
}
.lang p:lang(*-CH) {
  color: blue;
}
<div class="lang">
  <p lang="de-DE-1996">de-DE-1996</p>
  <p lang="de-CH">de-CH</p>
</div>

The Hyperlink Pseudo-class——:any-link 伪类

:any-link 用于匹配带有 href 属性的超链接元素,例如 <a><area><link> 等带有 href 属性的元素。:-webkit-any-link:-moz-any-link 是它的兼容性写法。目前工作组对该选择器的命名尚不满意,未来该选择器可能会修改其名字。该选择器的作用在于可以选出所有带有链接的元素,如果使用旧方法,那么只能使用标签名的方式或者 a[href=value] 的方式去匹配。

.link a:any-link {
  color: red;
}
.link a:-webkit-any-link {
  color: red;
}
.link a:-moz-any-link {
  color: red;
}
<div class="link">
  <a href="#">我是带有颜色的超链接</a>
</div>

The contextual reference element pseudo-class——:scope

:scope 用于匹配当前作用域下的顶级元素。但是目前 <style scoped> 已经被移除——issue,所以 :scope 基本等效于 :root

<div class="scope">
  <p>This paragraph is outside the scope.</p>
  <div>
    <style scoped>
      :scope {
        background-color: red;
      }
      p {
        color: blue;
      }
    </style>
    <p>This paragraph is inside the scope.</p>
  </div>
</div>

以上代码,第二个 div 将会有红色背景,并且他的所有 <p> 子元素都将拥有蓝色文字。

Time-dimensional Pseudo-classes——:current(), :past(), :future()

我个人用 时间轴伪类 统一称呼 :current(), :past(), :future() 这三个伪类。:current() 匹配时间轴当前的元素,:past() 匹配 :current()元素之前的元素,:future() 则匹配当前时间轴后的所有元素。这里说的时间轴指的是例如 WebVTT。值得注意的是,规范中写道如果使用的时间轴并不是文档语言所规定的,那么 :past():future() 有可能分别匹配 :current() 元素的前面的兄弟元素和后面的兄弟元素。由于在 Chrome Canary 和 Safari TP 上都不支持这几个伪类,所以无法实验正确性。下面使用的例子是从这个网址摘过来的。

:current(p, span) {
  background-color: yellow;
}

:past(p, span),
:future(p, span) {
  background-color: gray;
}
<video controls preload="metadata">
  <source src="http://html5demos.com/assets/dizzy.mp4" type="video/mp4" />
  <source src="http://html5demos.com/assets/dizzy.webm" type="video/webm" />
  <source src="http://html5demos.com/assets/dizzy.ogv" type="video/ogv" />

  <track label="English" kind="subtitles" srclang="en" src="http://www.iandevlin.com/html5test/webvtt/upc-video-subtitles-en.vtt" default>
</video>

The Indeterminate-value Pseudo-class——:indeterminate

radiocheckbox 元素上一般有两种状态——选中未选中,但是有的时候的状态会是不确定状态,而 :indeterminate 就是匹配这种不确定状态的 radiocheckbox

:indeterminate + label {
  background-color: gray;
}
<input type="radio" name="name" id="test">
<label for="test">未确定状态</label>

通常地,radiocheckbox 在没有声明选中状态时,默认只有两种可能性:checkedunchecked,为了让他们出现第三种状态,我们可以借助 JS 来控制:

document.querySelector('#test').indeterminate = true;

上面例子的 <label><input> 处于 indeterminate state 的时候,文字将会变为灰色。

The default-option pseudo-class——:default

:default 匹配一组相似元素集合中的默认元素,例如 <form> 中有多个 <input>,其中有一个是 <input type="submit">,那么该元素将会被匹配。此外还有 <option> 也有默认元素。

.default .default-form :default {
  background-color: gray;
}
<div class="default">
  <form class="default-form" action="#" method="get">
    <input type="submit" name="name" value="submit">
    <input type="reset" name="name" value="reset">
  </form>
</div>

The validity pseudo-classes——:valid, :invalid

<input type="email"> 中,如果我们输入了 abc123,那么此时 :invalid 将会匹配该元素,假如我们输入 abc123@163.com,那么此时 :valid 将会匹配该元素。这里要注意假如我们没有为 <input> 作约束,例如 <input type="text">,那么它的任意输入将使元素既不会被 :valid 匹配,也不会被 :invalid 匹配。

.valid input:valid {
  color: green;
}

.valid input:invalid {
  color: red;
}
<div class="valid">
  <input type="email" name="eamil_valid" value="abc@abc.com">
  <input type="email" name="email_invalid" value="abc">
</div>

The range pseudo-classes——:in-range, :out-of-range

:in-range:out-of-range 只对有被条件约束的元素起作用,例如 <input type="number" min="1" value="1">,如果输入数字小于 1,那么将会被 :out-of-range 匹配,反之则是被 :in-range 匹配。在很多时候,我们需要对“脏值”做一个高亮的显示,以前可能需要配合 JS 对值的边界进行检测,然后在对元素的样式进行修改。而现在,有了这两个伪类的存在,我们可以完全使用 CSS 来控制。

.range input:in-range {
  color: green;
}

.range input:out-of-range {
  color: red;
}
<div class="range">
  <input type="number" name="range" value="1" min="1" max="10">
</div>

The optionality pseudo-classes——:required, :optional

:required:optional 分别匹配带有 required 标识的元素和不带 required 标识的元素。同样地,我们可以利用这两个伪类来对需要填写的元素添加特定的样式。

.optionality input:required {
  color: green;
}

.optionality input:optional {
  color: red;
}
<div class="optionality">
  <input type="text" name="required" value="required" required>
  <input type="text" name="optional" value="optional">
</div>

The user-interaction pseudo-class——:user-error

:user-error 会匹配 :invalid, :out-of-range 和没有任何值的 :required 元素,但是假如是初始化时就触发这三种错误,user-error 将不会匹配该元素,只有当用户和元素进行交互或者提交了该表单并且触发了这三种错误,:user-error 才会被触发。Chrome 和 Safari 可能尚未支持,所以无法验证正确性。

.user-error input:user-error {
  color: red;
}

.user-error input:valid {
  color: green;
}
<div class="user-error">
  <input type="email" name="eamil_valid" value="abc@abc.com">
  <input type="email" name="email_invalid" value="abc">
</div>

The mutability pseudo-classes——:read-only, :read-write

:read-only 匹配不可被编辑的元素,:read-write 则匹配可被编辑的元素,例如 <input> 或者 contenteditable="true" 的元素。:-moz-read-only:-moz-read-write 分别是他们的兼容性写法。

.mutability :read-only {
  color: red;
}

.mutability :read-write {
  color: green;
}
<div class="mutability">
  <input type="text" name="read-write-input" value="read-write">
  <p contenteditable="true">read-write-paragraph</p>
  <p>read-only-paragraph</p>
</div>

The placeholder-shown pseudo-class——:placeholder-shown

:placeholder-shown 匹配 placeholder 文字显示时的 <input> 元素。::-webkit-input-placeholder, ::-moz-placeholder, :-ms-input-placeholder 分别是它在不同浏览器的兼容性写法。在此之前,原生的 placeholder 文字是没有方法去改变其颜色的,大多数做法是使用 value 来代替 placeholder,同时利用 JS 对 input 的 focus 事件进行监听,将 value 清空,从而达到一个模仿 placeholder 的效果。

.placeholder input:placeholder-shown {
  color: green;
}
.placeholder input::-webkit-input-placeholder {
  color: green;
}
.placeholder input::-moz-placeholder {
  color: green;
}
.placeholder input:-ms-input-placeholder {
  color: green;
}
<div class="placeholder">
  <input type="text" name="placeholder" placeholder="placeholder is green">
</div>

Grid-Structural Selectors

该特性将对例如 <table> 的栅格布局起作用。它包含 :column(selector), :nth-column(n):nth-last-column(n)。目前浏览器都还未支持,无法实验正确性。这些伪类将让栅格布局的样式控制变得更为简单,不过更多的试验要等到浏览器支持才能一一试验。

:column(selector)

:column(selector) 将匹配例如 <table> 中 带有 selector 类名的那一列的所有元素。

:column(.selected) {
  color: green;
}
<table>
  <col class="selected" />
  <col class="blur" />
  <col class="blur" />

  <tr>
    <td>A</td>
    <td>B</td>
    <td>C</td>
  </tr>

  <tr>
    <td>D</td>
    <td>E</td>
    <td>F</td>
  </tr>

  <tr>
    <td>G</td>
    <td>H</td>
    <td>I</td>
  </tr>
</table>

在上面的例子中,A、D、G 都将是绿色的。

:nth-column(n) 和 :nth-last-column(n)

:nth-column(n) 匹配括号内 n 的计算值的某一列的元素,计算方式是从头开始计算,而 :nth-last-column(n) 则是从后开始计算。

:nth-column(2n) {
  color: red;
}

:nth-last-column(3n) {
  color: green;
}
<table>
  <col class="selected" />
  <col class="blur" />
  <col class="blur" />

  <tr>
    <td>A</td>
    <td>B</td>
    <td>C</td>
  </tr>

  <tr>
    <td>D</td>
    <td>E</td>
    <td>F</td>
  </tr>

  <tr>
    <td>G</td>
    <td>H</td>
    <td>I</td>
  </tr>
</table>

Tree-Structural pseudo-classes——:blank

Tree-Structural pseudo-classes 是 CSS3 中的规范,但在 CSS Selectors Level 4 中加入了 :blank,它和 :empty 类似,区别在于 :empty 只能匹配没有任何内容的元素,而 :blank 可以匹配带有 spaces(空格), tabs(缩进符)segment breaks(段落过段) 内容的元素。

Combinators——>>

A >> B 匹配祖先元素为 A 的 B元素,其用法与 A B 一样,与 >, +, ~ 用意一样,不过意义不同。


上面的特性都已经存在 Working Draft 中,还有一些 Editor’s Draft 的特性,也顺带一提。

The Relational Pseudo-class——:has()

:has(selector) 匹配含有 某些规则 的元素。

下面例子将匹配含有 img 子元素的 a 元素:

a:has(> img)
/* 将匹配拥有 dt 兄弟元素的 dt 元素 */
dt:has(+ dt)

下面例子将匹配不含有 h1、h2、h3、h4、h5、h6 元素的 section 元素:

section:not(:has(h1, h2, h3, h4, h5, h6))

和上面例子不同,下面交换了两个伪类的嵌套,表示匹配含有的不是 h1、h2、h3、h4、h5、h6 子元素的元素,区别在于这种写法要求必须含有一个子元素,而上面的写法可以不含有子元素也会被匹配:

section:has(:not(h1, h2, h3, h4, h5, h6))

The Drag-and-Drop Pseudo-class——:drop, :drop()

:drop:drop() 匹配可被放置拖动元素的目标元素,两者区别在于 :drop() 可以匹配一些规则,包括 active, valid, invalidactive 会匹配可被放置的目标元素,valid 匹配放置的元素为合法元素的目标元素,invalid 反之。如果 :drop() 括号里没有任何过滤,那么将和 :drop 没有区别。

最后

文章介绍了目前 CSS Selectors Level 4 的一些新的特性,我们看到 CSS 正在逐渐将以前需要依赖 JS 做到的事情转化为 CSS 自身能够处理的过程,这个将大大降低了 CSS 和 JS 之间的代码耦合,从而也降低了项目迭代过程中的维护成本。从 1996 年发表的 CSS1 规范至今已经过去了 20年,从浏览器厂商的各自为战到现在各个浏览器厂商遵守规范进行 CSS 新特性的开发,可以说现在前端因为浏览器兼容性的原因所造成的开发成本已经不如以前了,当然,现在还存在许多需要与 IE8+ 打交道的网站,但是这个比例已经大大降低了。还有新的挑战来源于手机端 Web 的兴起,手机的性能远远不及 PC 的性能,而国内手机浏览器内核的百花齐放又再一次让兼容性这个严峻的问题摆在了前端开发者眼前,开发者们可以在开源社区中寻找各种 polyfill 来消除这种差异,也可以利用各种 CSS hack 来解决不同浏览器的兼容性,但是不可避免地会对性能造成影响。

其实说到了 CSS,不得不说与 CSS 密不可分的 JavaScript,JavaScript 自从 ES6 的发表,解决了 JavaScript 某些方面多年来混乱的局面,EcmaScript 的版本更新频率也变得勤快了起来。在 ES6 中我们可以看到的是其解决很多 ES5 上存在的问题,例如异步事件处理,其新增了许多方法我们都发现 JQuery、underscore 等知名库的影子,所以说,开发者的热情是推动规范的不可或缺的中坚力量。规范的制定不再是一言堂,开发者们也有了说话和建议的余地。

无论如何,相信在各大厂商的和 W3C 工作组的推动下,未来不管在 CSS 还是 JavaScript 上,将会逐渐走向规范上的统一,让我们拭目以待吧!

参考资料:

https://www.w3.org/TR/2013/WD-selectors4-20130502/
https://drafts.csswg.org/selectors-4/
http://css4.rocks/selectors-level-4/
http://css4-selectors.com/selectors/

评论