You signed in with another tab or window.
Reload
to refresh your session.
You signed out in another tab or window.
Reload
to refresh your session.
You switched accounts on another tab or window.
Reload
to refresh your session.
By clicking “Sign up for GitHub”, you agree to our
terms of service
and
privacy statement
. We’ll occasionally send you account related emails.
Already on GitHub?
Sign in
to your account
CSS 的原生嵌套
嗨,你好,我叫
安佳
,是 360 搜索事业部的一名前端开发工程师。今年5月,我有幸加入了 W3C 的 CSS 工作组,成为其中的一员。第一次参与工作组讨论的是关于
[css-grid-2] Allow minmax where max wins over min
,很开心
自己的提议
被标准采纳了,预计会在
CSS Grid Layout Module Level 2
里实现。
期间,看到了一个关于要 CSS 支持原生嵌套的议题
[css-nesting] request to pick up the css-nesting proposal
。当时觉得这个特性很好,于是就表达了
自己的观点
。一周之后,CSS 工作组在周会上对它进行了讨论,但是打的标签依然是
unknown/future spec
。又过了两周,看起来并没有要在下一个模块里实现此特性的计划,也没有要商讨的安排。
这么有用的嵌套功能和
scope
特性,为什么一直坐在冷板凳上呢?于是,我就想探究下原因。这篇文章就是研读此 Issue 及相关规范的成果,主要有这三部分:
来自 Web 开发者的呼声:介绍此 Issue 的相关背景
CSS 工作组都干了什么:介绍工作组的工作内容
未来的原生嵌套:介绍嵌套语法
来自 Web 开发者的呼声
2012年4月13日,
CodePen
的联合创始人
Chris Coyier
抱怨 CSS 的类名不支持命名空间,导致要写好多重复的选择器。
2016年2月2日,微软的项目经理
Kenneth Auchenberg
说如果 CSS 支持了变量和嵌套,他将不再使用预处理器。
2016年12月8日,
《CSS揭秘》
的作者
Lea Verou
调研了使用 CSS 预处理器的首要原因(单选题),有 1838 个人参与了投票,最终并列第一的两个理由是嵌套和变量。她觉得是时候该重新考虑 CSS 原生嵌套的问题了。
2017年7月13日,集设计和开发才能于一身的 UI/UX 自由工作者
Sara Soueidan
说嵌套是她最想要的 CSS 功能。
2017年8月15日,
node-inspect
的作者
Jan Olaf Krems
说
cssnext
把嵌套定义成了“明天的 CSS”,但他还是想看到原生的 CSS 嵌套,毕竟 JS 的生态系统已经证明避免“每个人都使用自己的半标准语言”绝对是健康的。
2018年2月23日,
Lea Verou
再次发声,说她现在还在用 CSS 预处理器写嵌套,一旦 CSS 支持了原生嵌套,她就果断弃用预处理。
2018年5月25日,
postcss-preset-env
的作者
Jonathan Neal
再次提议
重新考虑下让 CSS 支持原生嵌套
(也就是本文章的切入点),这引来了一波热议。
CSS 工作组都干了什么
其实,早在2014年4月3日,W3C 就发布过一个
CSS范围(scoping)模块
的工作草案;2015年9月23日,谷歌的工程师
Tab Atkins
也发布过一个
CSS嵌套模块
的编辑草案。那个时候,CSS 工作组也讨论过嵌套的问题,但并未通过社区的同意(见
会议纪要
)。
针对 Jonathan Neal 这次的提议,CSS 工作组的讨论流程如下:
图1. CSS工作组的讨论流程
要支持原生嵌套
嵌套的样式规则是一个普遍的诉求
现存的 CSS 预处理器都支持写嵌套,且它是最受欢迎的功能之一
有了原生嵌套,就可以不用预处理器了
决定仅增加嵌套语法糖
开发人员已经习惯了预处理器中的嵌套,嵌套选择器不应该有特殊的优先级
局部样式是有用的,但
scope
能否真正满足开发人员的需求还不明朗
嵌套和
scope
是两个维度的特性,建议先实现已经比较成熟的嵌套
嵌套语法,详见下节
最终的结论
是,新增 CSS 嵌套模块,默认 ED(Editor Draft,编辑草案) 阶段,由
Tab Atkins
担任编辑,并收集相关 Issues,直到该特性成为 FPWD(First Public Working Draft,首个公开工作草案)。
ED
Editor Draft 编辑草案
WD
Working Draft 工作草案
CR
Candidate Recommendation 候选推荐标准
PR
Proposed Recommendation 提议推荐标准
REC
Recommendation 推荐标准(最稳定的)
关于标准的诞生过程,可查看
World Wide Web Consortium Process Document
未来的原生嵌套
CSS Nesting Module Level 3
里定义了 CSS 嵌套,它新增了一个新的选择器:嵌套选择器
&
a, b {
& c { color: blue; }
/* 等价于 */
a c,
b c { color: blue; }
看了上面的写法,我想肯定有小伙伴要问了:那个前缀
&
能省略不写吗?
对此,草案里的解释是:现有的 CSS 解析都是通过一个单独的前瞻符(lookahead token)来区分各种选择器的,如果新增的嵌套语法不写前缀的话,那一段文本就没法提前知道它到底是一个 CSS 声明还是一个 CSS 选择器了,这会非常不利于浏览器的实现。
前瞻符,诸如:
#
ID 选择器
.
类选择器
[]
属性选择器
*
通用选择器
::
伪元素
图2. CSS 声明
另外,如果省略了
&
也就没法区分
#foo { .bar {} }
到底是复合选择器
#foo.bar
还是组合选择器
#foo .bar
了。
草案里定义了两种嵌套方法:直接嵌套和
@nest
规则
直接嵌套,即直接以嵌套选择器
&
开头
.foo {
color: blue;
& > .bar { color: red; }
/* 等价于
.foo { color: blue; }
.foo > .bar { color: red; }
.foo {
color: blue;
&.bar { color: red; }
/* 等价于
.foo { color: blue; }
.foo.bar { color: red; }
.foo, .bar {
color: blue;
& + .baz, &.qux { color: red; }
/* 等价于
.foo, .bar { color: blue; }
.foo + .baz,
.bar + .baz,
.foo.qux,
.bar.qux { color: red; }
* 以下写法都是无效的
.foo {
color: red;
.bar { color: blue; }
/* 无效原因:没有嵌套选择器 & */
.foo {
color: red;
.bar & { color:blue; }
/* 无效原因:& 没有在组合选择器的第一位 */
.foo {
color: red;
&.bar, .baz { color: blue; }
/* 无效原因:列表的第二个选择器里没有嵌套选择器 & */
@nest
规则
.foo {
color: red;
@nest & > .bar {
color: blue;
/* 等价于
.foo { color: red; }
.foo > .bar { color: blue; }
.foo {
color: red;
@nest .parent & {
color: blue;
/* 等价于
.foo { color: red; }
.parent .foo { color: blue; }
.foo {
color: red;
@nest :not(&) {
color: blue;
/* 等价于
.foo { color: red; }
:not(.foo) { color: blue; }
* 以下写法都是无效的
.foo {
color: red;
@nest .bar {
color: blue;
/* 无效原因:没有嵌套选择器 & */
.foo {
color: red;
@nest & .bar, .baz {
color: blue;
/* 无效原因:列表里并非所有的选择器都包含嵌套选择器 & */
在此特别感谢,感谢
@cncuckoo
(李松峰老师)对本文提出的大量指导建议,让我学到了很多,尤其是极其严谨的工作态度,以及以读者为出发点的行文思路。
你有想说的吗?
看完本篇文章,你有什么想说的吗?欢迎留言。
当然,你也可以去 CSS 工作组的官方 github 上
w3c/csswg-drafts
提 Issue。若是和嵌套相关的,则 Issue 的标题格式是“[css-nesting]...”;若是和
scope
相关的,则标题格式是“[css-scoping]...”。诚邀大家提出建设性的意见/建议。
相关规范:
CSS Nesting Module Level 3
CSS Scoping Module Level 1
CSS Cascading and Inheritance Level 3
本文章仅代表个人观点,与 CSS 工作组无关。特此说明
@INCHMAN1900
对,是的。草案里必须写
&
,更多的是考虑到浏览器的实现,没它就没法“提前识别”对象进而分发给不同的模块来处理了(这点 Tab Atkins 应该有和浏览器开发人员交涉过,见
comment1
和
comment2
,以及这里
Nesting Style Rules
)。
我也有思考过这个问题,为什么预处理器可以省略
&
,但原生就不可以。我个人觉得,除了上面提到的解析器的内部策略之外,还有一点,就是:预处理器可以做纯字符串处理,而不需要考虑语义,eg.用
{
和
}
来识别嵌套就可以。但是原生要支持的话,就不能说先将“嵌套写法”变成“正常写法”再处理,而是直接能识别语义。所以其实我还挺好奇,原生嵌套选择器最后在开发者工具
Elements/Styles
里会是个什么样的呈现方式。
@yisibl
嗯,是,对
@nest
的使用场景有同感。我能想到的一种场景就是:当一个UI组件要用到特定页面的时候,在这个组件内部这么写
.parent1 @nest {}
,以适配不同的上下文。大约是这种“子父,倒着用”。“父子,正着用”的话,可读性是会...很无法想象,挺别扭的。
顺便补充~:
刚再读了下草案里的那段
@nest
说明,说对它的限制会比直接嵌套少点,也可以明显地标识出它就是个嵌套,再就是嵌套位置灵活 ^^
PS. 用 SCSS 以后好无缝切换 哈哈
@INCHMAN1900
挖个坑:CSS 处理器会继续普遍使用并长期使用下去。
@LDWX
有优势,最明显一点就是可以减小最终 CSS 文件的大小。
@anjia
现在比较担心的是原生实现嵌套太多层级导致生成文件大小变的失控的问题,这个在预处理器中也存在,不过因为会经历编译的过程,我们会看到最终生成文件的体积。Firefox 的开发者对此提出了这个 issue:
w3c/csswg-drafts#2881
引号去掉更佳哦~