以下是来自 Salsita Software 的 Filip Naumovic 的客座文章。Filip 创建了 一个 Sass 工具 来帮助解决我知道我经常遇到的问题。您很高兴地在 Sass 中进行嵌套。您可能已经深入了两三层,并且需要根据**某些父选择器**来设置变体的样式。您需要要么跳出嵌套并开始一个新的嵌套上下文,要么使用 `@at-root` 进行彻底更改。我会让 Filip 来讲述他的新工具改变了这一切的故事。
Sass 在清理我们的 CSS 代码库方面帮助了我们中的许多人。当它出现在场景中时,一些非常有用的模式出现了,并迅速被采用。
例如,`&` 的用法 和嵌套允许清理原本相当糟糕的代码块。
这个
.my-app { display: block; }
.my-app .widget { border-radius: 5px; }
.my-app .widget.blue { color: blue; }
.isIE6 .my-app .widget { background-image: url('fake-borders.png'); }
@media (max-width: 768px) { .my-app .widget { float: left; } }
变成了这个
.my-app {
display: block;
.widget {
border-radius: 5px;
&.blue {
color: blue;
}
.isIE6 & {
background-image: url("fake-borders.png");
}
@media (max-width: 768px) {
float: left;
}
}
}
这对我们来说是一个多么积极的变化!
所有 `widget` 元素的样式变体都清晰地嵌套在自身下面,使用缩进作为相关性的视觉提示和查询生成器。
在这种情况下,当前选择器(`&`)为最常见的模式提供了一种快捷方式。对元素的变体进行样式设置,该变体是通过元素本身的属性或预先添加的父状态调用的。
嵌套的媒体查询是一个比较新的补充,但它们暗示着向样式缩进语法演变几乎是自然而然的。它易于阅读和导航,因为它以某种方式反映了熟悉的 DOM 结构,并将一个元素的所有样式都放在一个地方,同时仍然生成我们宝贵但有时很复杂的 选择器。
如今,嵌套和当前选择器功能存在于 Less、Sass 和 Stylus 中。喝点酒,几乎可以称之为标准。
一个经典的“你不能那样做”案例。
以上面的代码块为例,让我们添加 `my-app.expanded .widget` 的样式。
尽管我们拥有强大的工具,但我们很快发现自己只有有限的选择
选项 1
使用现代的 `@at-root` 指令(或 Stylus 中的 `/`),我们完全离开当前范围并重复完整的根查询以将相关的新的样式嵌套在 `widget` 下面,因为当前选择器无法帮助我们表达这种关系。
.my-app {
display: block;
.widget {
border-radius: 5px;
&.blue {
color: blue;
}
.isIE6 & {
background-image: url("fake-borders.png");
}
// repeating the root selector here
@at-root .my-app.expanded .widget {
color: red'
}
@media (max-width: 768px) {
float: left;
}
}
}
这会创建难以阅读的代码,并且存在很多重复,尤其是在实际使用远远超过我们的小示例片段时。但是,它保持了我们辉煌的嵌套范式。
选项 2
我们在 `my-app` 下面创建一个新的代码块,并使用它来更改与 `expanded` 状态相关的 所有子元素。这意味着我们的 `widget` 现在在不同的位置进行样式设置,并且这种分离随着嵌套中每个元素中添加的每个状态而增加。
.my-app {
display: block;
.widget {
border-radius: 5px;
&.blue {
color: blue;
}
.isIE6 & {
background-image: url("fake-borders.png");
}
@media (max-width: 768px) {
float: left;
}
}
&.expanded .widget
color: red;
}
}
虽然这直接违背了我们的“将所有相关样式嵌套”梦想,但这是我们学会忍受的缺陷。你们中的许多人甚至可能会为这种模式辩护,因为它已经成为做事的方式很长时间了。
但是,为了选择起见,如果有一个*选项 3*,那会不会很棒?一种允许我们表达对 `my-app.expanded` 的简单更改,这种更改会影响我们的 `widget`,而无需逃离上下文?
这个想法在我心中困扰了相当长一段时间,即使只是出于我对自己的样式表的一种强迫症。我已经把它作为我的副业,试图在样式仓库中找到这个缺失的工具。
寻找选项 3
在深入研究这个主题时,我发现蜘蛛网、永恒的讨论和各种各样的建议,其中许多建议在当前选择器字符 `&` 中添加一些特殊语法。这样做意味着要花几个月的时间学习复杂的核心库,并进行 漫长的战争,这立即感觉像一个不可接受的负担。
其次,我认为 `&` 工作得很好,因为它清楚地表示了整个上下文,因此为它添加更多功能可能会存在问题。它只做一件事,而且做得很好,因此在目前看来,为它创建一个好伙伴似乎是一个更好的主意。
为了方便集成,我决定在预处理器语言级别实现这个想法,这样您就可以直接 `@import` 并使用它。如今,预处理器是功能强大的框架,所以为什么不呢?
我的首选是 Stylus,因为它太棒了。不幸的是,由于 问题 1703,当前选择器占位符目前无法在 mixin 函数内部修改。就像一个虔诚的信徒,我会一直等到 Stylus 修复它,但我必须继续寻找我可以*现在*实现的东西。
正如我所知,您不能解析 Less 中的当前选择器,所以它也被排除了。
另一方面,SassScript 被证明是一个强大的工具。虽然它缺乏许多用于字符串和数组操作的有用抽象,但手动制作这些函数是完全可能的。许多函数 已经由 Sass Prince Kitty Giraudel 提供。
经过几个月的受控字符串恐怖……
inStyle for Sass 3.4+ 诞生了!
我知道,这个名字很俗气。但它暗示了功能,因为您希望它在实际代码中可读。Mixin 语法对预处理器用户来说已经很熟悉,因此拥有一个暗示性的名称来描述元素父级中的更改对我来说听起来很合理,因为它可以增加抵御陌生感的缓冲。
无论如何,所有这些都必须保持可读性,同时处理复杂的情况,否则它会失去目的,转而使用 `@at-root` 选择器方法或将代码嵌套在其他地方。我决定使用两个基本机制,我相信它们可以满足最卑鄙的需求,同时保持逻辑上简单的解析算法
使用 1)修改
对当前选择器中存在的复合元素的添加被证明可以处理大约 80% 的实际代码,就像我们的第一个示例试图实现的那样。
.my-app {
display: block;
.widget {
border-radius: 5px;
&.blue {
color: blue;
}
.isIE6 & {
background-image: url("fake-borders.png");
}
@include in(".my-app.expanded") {
color: red; // .my-app.expanded .widget { };
}
@media (max-width: 768px) {
float: left;
}
}
}
尝试这样阅读
在 `my-app.expanded` 状态中设置 `widget` 的样式。
该函数从嵌套底部到顶部搜索 `my-app` 元素的第一个出现位置(跳过当前元素),并将类 `expanded` 附加到它,返回一个新的选择器。
更长的查询和组合修改怎么办?
table {
table-layout: fixed;
thead {
font-weight: normal;
tr {
height: 30px;
td {
background-color: #fafafa;
&.user {
font-weight: bold'
}
@include in('table.one tr.two:hover') {
background-image: url(rainbow.png) // table.one thead tr.two:hover td { };
}
}
}
}
}
找到 `tr` 父级并使用 `two:hover` 进行修改。向上移动,还找到 `table` 并使用 `one` 进行修改,其他元素被跳过。
从新选择器中删除无关的多选择器
ul, ol {
list-style: none;
li {
display: inline-block;
a {
text-decoration: underline;
@include in("ol.links") {
color: orange; // ol.links li a { };
}
}
}
}
不可能的情况和无效的 CSS 查询会在编译时产生阻塞的 Sass 错误
table {
table-layout: fixed;
td {
height: 30px;
@include in("table^s&()#") {
what: the; // ERROR, invalid CSS
}
@include in ("tr.green:hover") {
border-color: green; // ERROR, no tr or tr.green to modify in &
}
}
}
在生产中进行崩溃测试(哈哈!)时,我发现另一个非常实用的需求,我无法仅通过修改父树来满足。事实上,它解决了上面的示例,因为您必须能够使用 `tr.green:hover` 来做到这一点。您只需要能够说出在哪里。
使用 2)插入
让我们假设以下内容
table {
table-layout: fixed;
thead {
font-weight: normal;
}
tr {
height: 30px;
}
td {
background-color: #fafafa;
}
}
您理想情况下会将 `table thead tr` 选择器嵌套在哪里?根据教条,您似乎必须像这样添加它
table {
table-layout: fixed;
thead {
font-weight: normal;
tr {
height: 50px;
}
tr {
height: 30px;
}
td {
background-color: #fafafa;
}
}
然而,所讨论的样式化元素是tr
,你已经把它作为通用样式了,所以理论上,把它嵌套在自身下面作为变体,可能更接近你对关系的思考方式,填补了当前选择器&
无法描述的空白。
在这种情况下,这意味着必须有一种简单的方法,可以在当前元素的某个位置插入一些选择器,同时还允许与复合修改组合。我无法想象在不添加特殊字符的情况下实现这一点,所以我选择了视觉上暗示的^
插入符。
table {
table-layout: fixed;
thead {
font-weight: normal;
}
tr {
height: 30px;
@include in("^thead") {
height: 50px; // table thead tr { };
}
}
td {
background-color: #fafafa;
@include in("table.blue-skin ^tbody") {
background-color: blue; // table.blue-skin tbody td { };
}
}
}
在这种情况下,插入符将thead
插入到当前或上次修改元素的上一级。更多的插入符意味着在当前选择器中进行更高的跳跃。
main {
display: flex;
> div {
flex-grow: 1;
span {
display: inline-block;
&.highlight {
outline-style: dashed;
@include in("^^.section.active") {
outline-style: solid; // main .section.active > div span.highlight { };
}
@include in("^^^.section") {
some: thing; // ERROR, inserting too high, it would equal to ".section &"
}
}
}
}
}
注意:&.highlight
与span
是同一个元素,所以插入将其视为嵌套中的一个步骤。
我认为 inStyle 在最简单的用例中表现出色,而这些用例也是最常见的。但如果需要,事情会变得更复杂。
使用 3) 高级组合
匹配算法允许你更疯狂地一次插入或修改多个复合元素。
.my-app {
display: flex;
section {
flex: 1;
header {
width: 100%;
@include in("^^.wrapper ^.dialog)") {
height: 50px; // .my-app .wrapper section .dialog header { };
}
@include in("^.overlay ^.sidebar") {
position: fixed; // .my-app section .overlay .sidebar header { };
}
@include in("^.modifier section.next ^.parent") {
opacity: 0; // .my-app .modifier section.next .parent header { };
}
}
}
}
.dialog
插入到header
的上一级,.wrapper
插入到上两级。.sidebar
插入到header
的上一级,.overlay
直接插入到其上一级。- 将
.parent
推到header
的上一级,用.next
修改section
,然后将.modifier
推到其上一级。
这提醒我,也许你有一些反馈! 我一直在考虑在你想将多个复合元素直接彼此相邻插入时启用一些更简单的语法,比如@include in("^(.overlay .sidebar)")
,或者改进解析器并启用更自然的@include in("^.overlay .sidebar")
。告诉我你的想法!
使用一段时间后,我发现我大多数不方便的代码模式都可以通过简单地更改这里和那里的一个元素,或者将新的选择器推到某个位置并保持原位来轻松解决。不过,我必须诚实地说,就这个想法的本质而言,它可能会对你的常规代码组织产生相当大的影响。
我理解使用 inStyle 可能会引发激烈争论。我的同事似乎要么思想开明,要么不在乎,这两种情况都很棒。
如果你使用它,我希望正确的处理方式与任何其他工具一样:当它适合工作时使用。大量使用复杂的嵌套 mixin 可能不如直接编写完整的查询更易读,但另一方面,它可以简化大多数现实世界中的问题,同时保持精简的占用空间。
在不久的将来,我想让 Stylus 移植版本正常工作,也许还会创建一个 Atom 编辑器插件,在代码中提示显示生成的查询。
尝试解决 CSS 的“第一世界问题”很有趣,我希望你能认为这个主题至少值得讨论。这个项目是开源的,所以随时加入代码或反馈!
无论你喜欢还是讨厌,它都在 GitHub 上,这里有一个小小的 微型网站,这里还有 实时调试器,以备不时之需。
查看 CodePen 上 Salsita Software (@salsita) 编写的 inStyle 崩溃测试假人。
感谢阅读!
题外话:是否可以以一种即使更改网站字体大小也能保持在同一行的方式设置代码行的突出显示?也许使用 em 而不是 px?
酷炫的讨厌评论。
我将在额外的依赖项和有用的语法之间左右为难。
这是我最喜欢的#1 东西
保持相同级别的嵌套感觉有点酷。
是的,我之前是黑粉。抱歉。文章中的想法很聪明,执行得很好。我只是最近对过度设计的 Sass 嵌套感到很困扰。我的意思是,即使在这篇文章的第一个例子中,冗长的 CSS 似乎也比嵌套的 SASS 更可取。(尽管我知道情况并非总是如此。)
Chris,对我来说,你的例子感觉就像这样更好
简单明了的 CSS 胜出。
我想说,如果你总体上更喜欢标准 CSS,并像你最后一个例子那样使用完整的查询,那么这个想法对你的应用来说就变得毫无意义了。
Chris,我认为你写了完美的反例。当你或其他人将导航从
ol
更改为ul
时,并且忘记更改字符串中的选择器,会发生什么?:)这是一个更易读的版本,没有
@include in("^headache")
是的,你仍然可以通过更改其中一个
li
选择器而没有更改其他选择器来搞砸。但是 - 在这里查看所有li
选择器要比查看嵌套得很深且在字符串内部的父选择器容易得多。说真的,为什么对嵌套如此痴迷?还有字符串中的选择器,太恶心了!
嗨,Agop,我不确定是否可以解决有人更改 HTML 却没有更改 CSS 的情况。
Filip,我的注释是针对在 HTML 和 (S)CSS 中都将
ol
更改为ul
,但不在@include in("foo")
字符串中的情况。对于不熟悉代码库的开发人员来说,这很不直观。我认为重复父选择器而不是子选择器是倒退了一步。你说得对。我认为必须重复父复合元素会提高可读性,但在评论中 Serg 进一步的请求后,我现在在 1.4.0 中添加了一种方法来抽象父目标,因此无需重复。特殊字符也是可配置的,它将正确引用父元素的任何更改。
为了记录,重复复合选择器有一些好处,而抽象它用
<
则无法获得这些好处。你可以解决多选择器,并且像文章中提到的那样保持相当的可读性。
好的,很酷,这样好多了。现在你不需要重复任何东西,这太棒了。
(但我仍然不确定是否会支持这种语法,但对那些喜欢它的人来说,更多力量。)
这是一个非常酷的概念。我绝对想在我的下一个项目中应用这个技巧。
对于那些担心 HTML 和 CSS 在修改标记等时出现冲突的人,我强烈建议观看关于模块化 CSS 的演讲:https://www.youtube.com/watch?v=HoQ-QEusyS0
嗨!我刚刚发布了一个 POSTCSS 插件,它使用非常相似且可配置的“^&”语法在嵌套 CSS 中引用祖先选择器。
看看它:postcss-nested-ancestors。
干杯!
嗨,Andrea,这太棒了!我也在研究将 inStyle 移植到 PostCSS,而你给了我勇气:D 我们似乎在使用类似的方法来分配父元素(向上计数当前元素),尽管我对
&
的使用有点犹豫 - 我已经习惯了它引用整个字符串了。无论如何,很高兴看到这些解决方案的兴起,因为我相信它们对我们的代码库很有价值。我想要的不是另一个
@include-operator(selector)
东西,而是对 Stylus 的/
进行扩展,使用我们都熟悉的“语法”:好的老../
路径遍历表示法。这将很容易地允许向上移动一级、两级或三级,这样就不再需要“核”选项,也不需要再次向下遍历整个选择器路径。
(而且,“但是如果我需要向上移动更多层级,那么这会变得非常难看,而且难以阅读”的反对意见可能只是意味着你已经开始让选择器变得太长了 ;-)
Stylus 已经有了针对特定父元素的方法,请参阅 https://stylus.org.cn/docs/selectors.html#partial-reference。然而,它在实践中有点乏善可陈,而且无法解决你可能需要的各种用例。
如果现在 BEM 是首选方法(直到 CSS 模块或影子 DOM 完全可用),我们真的需要这个吗?
嗨,作者在这里。我认为与 BEM 没有关系,你能详细说明一下吗?
使用 BEM,您应该尽量避免嵌套。本文中展示的方法,却大量使用了嵌套,以至于您需要一个自定义的 mixin 来为嵌套选择器添加灵活性。
我认为“嵌套所有内容!”不是编写干净 CSS 的好方法……BEM 教会了我们这一点。
您能说明一下与 BEM 的冲突吗?如果您指的是简短且极度具体的查询,您仍然会遇到与任何其他方法相同的情况。我认为 InStyle 只是在您使用嵌套 CSS 时为您提供了一些更方便地描述这些情况的方法,但不会干扰方法本身。
也许我错过了一些东西(肯定是的),但是……
@include in(".my-app.expanded") {
和.my-app.expanded & {
之间有什么区别?拥有一个类似 BEM 的 CSS,我会这样做
这将减少嵌套地狱。
BEM 代码使用当前选择器来存储前缀,而其他方面则不多,这既有优点也有缺点。这是一种权衡,为 BEM 使用当前的 inStyle 会产生相同的结果——您无需重复当前元素,并且可以获得变体嵌套,但会失去
my-app
的插值(不过您在 BEM 中并不需要它,因为您希望查询结束得更短)。这等同于您的代码´´´
.my-app
&__widget
color: red
+in(‘.my-app–expanded’)
color: blue
´´´
我可以看到这一点可以进一步优化,也许有一个针对 BEM 的版本。
由于 BEM 具有固定的语法规则,状态可以自动添加前缀。现在,这将直接减少完成相同操作的代码量,而且我认为可读性也更高。inStyle 目前还没有做到这一点,但添加它应该不是问题。您认为这是改进吗?
我在这里的偏好
通过不使用 & 选择器来保持代码可搜索。根据需要在这里添加的属性数量,我可能会这样写
或者就这样
抱歉连续三次评论,但还值得注意的是,动态状态将使用这样的单独类
您好 Michael,您的示例正是我的目标。您喜欢的语法似乎基本上是默认的 CSS,您重复了大量选择器,而没有真正利用嵌套带来的优势。
我的示例很差,我道歉。我认为在 CSS 源代码中将类名分成两个部分不是一个好主意——这会对代码可搜索性造成负面影响。关于这篇文章,这是一个我经常遇到的重要问题!很高兴看到这个实验性功能。
您好 Michael,我为一开始误解您的评论而道歉——我现在明白了,您在质疑上面的 BEM 示例使用当前选择器的方式。我同意,以这种方式插值选择器查询看起来相当可疑。可能既存在嵌套地狱,也存在 BEM 地狱,两者都应该避免。我认为,如果有人决定使用 BEM,则可以在预处理器框架内抽象掉不好的部分,但这并不在 inStyle 的直接范围内。
谢谢,这个想法和插件都很好。
我遇到了这样的问题,如果
^^.wrapper
被解释为.my-app.wrapper
,对我来说会非常有用,但目前它是
.my-app .wrapper
(我预计^^ .wrapper
会有这样的行为)它不提供任何与父元素交互的方法,只能添加中介。
您好 Serg,感谢您的反馈!我一直在考虑添加简写语法来定位父元素,而无需重复它们的复合选择器,但决定暂时不实现它,因为在实践中完整的选择器似乎更具可读性。同时,
^
字符似乎很好地表明了插入功能,因此也许我们可以找到一些替代字符来表明目标复合。可能是数字?我将在 Github 上为此话题创建一个问题,所以请随时加入讨论!无论如何,这都不是问题。我使用 BEM 语法,就像其他几位评论者提到的那样。我为自己制定的规则之一是“每个模块部分只声明一次”。但您是对的,当您处理模块的特定状态时,如何处理 Sass 组织并不十分清楚。我经常同时使用方法 1 和方法 2。
我认为,这个插件在谨慎使用的情况下,可以让我将模块部分状态特定的样式保留在模块原始样式旁边。我非常喜欢这一点。
我喜欢这个想法和执行。
它有用吗?对我来说有用。我几个月来一直在寻找这样的解决方案。
它必不可少吗?当然不是,但它在我工具箱中真的很棒。
当然,它可能很棘手,过度使用会导致大量混乱的代码。但许多常见的预处理器功能也是如此。例如,一个基本的 SASS @extend。我见过很多糟糕的用法,但它就在那里,它很有用,我认为这不是问题。
就像所有工具一样,它应该谨慎使用,只有在有帮助的时候才使用。
我的天,这看起来很棒!我之前在一个项目中遇到了完全相同的问题。我尝试了使用 mixin 的不同方法,但从未成功。我选择了方法 2(@at-root 对我来说看起来太乱了)。我一定会将您的插件加入书签,并在我的下一个 CSS 密集型项目中使用它。
有人说这会导致混乱的代码。当然可以,但人们可以滥用任何类型的功能,Sass 也不例外。我曾经在一个项目中,人们疯狂地使用 @extend(我只在使用占位符时使用它,而且非常少)。我可以告诉你,由于 extend 规则数量带来的复杂性,我甚至无法阅读那些文件。
非常感谢。感谢分享!
怎么样?
我认为您的 mixin 基本上等同于
有用,但不比文章中描述的插件好。