如何在 Sass 中方便引用祖先选择器

quote

We must strive to become good ancestors.

Ralph Nader

Sass 提供了双亲选择器 & 用以在嵌套选择器中引用外部的选择器,本文聊聊如何进一步往外引用,为当前选择器添加祖先选择器。

双亲选择器

在 Sass 中双亲选择器(Parent Selector 有时也叫父选择器) & 是一个十分有用且常用的选择器,它可以复用外部选择器的名字,从而更轻松地实现多重样式编写。

.btn {
  background: transparent;

  &:hover {
    background: grey;
  }
}

会输出

.btn {
  background: transparent;
}
.btn:hover {
  background: grey;
}

祖先选择器

有时候我们遇到这样一种模式,如主题样式,在元素根处可能有 .dark-theme 来说明目前处于黑暗模式;或者使用 Modernizr 检测浏览器特性,在根元素会根据环境添加相应 class 表示特性支持情况。这时候我们写样式可能就需要拆分开来写。

.btn {
  background: transparent;

  &:hover {
    background: grey;
  }
}

.dark-theme .btn {  background: linear-gradient(cornflowerblue, rebeccapurple);}

这里有两点不太舒服的地方:

  1. 处理同个逻辑的样式需要拆开写。
  2. 与祖先选择器名耦合,不方便修改。

我们来看看如何解决这两个痛点。

@at-root

在 Sass 中有一个 at 规则叫 @at-root,它可以跳出当前嵌套直接在文档根输出内容。

.btn {
  background: transparent;

  &:hover {
    background: grey;
  }
  
  @at-root .dark-theme & {    background: linear-gradient(cornflowerblue, rebeccapurple);  }}

会输出

.btn {
  background: transparent;
}
.btn:hover {
  background: grey;
}
.dark-theme .btn {
  background: linear-gradient(cornflowerblue, rebeccapurple);
}

但这里依然没有解决祖先选择器名耦合的问题,于是我们进一步抽象。

Mixin

将以上用法封装为 mixin 即可达到复用。

@mixin dark-theme {
  @at-root .dark-theme & {
    @content;
  }
}

.btn {
  background: transparent;

  &:hover {
    background: grey;
  }
  
  @include dark-theme {
    background-image: linear-gradient(cornflowerblue, rebeccapurple);
  }
}

支持修饰符

一些过渡库,如 Vue transition 和 React Transition Group 会设置一系列的类型名,如 .fade-enter.fade-exit,在 Sass 中我们可以直接拼接 &-enter 进行复用,现在让我们的 mixin 也支持:

@mixin dark-theme($modifiers...) {
  @if length($modifiers) > 0 {
    @each $modifier in $modifiers {
      @at-root .dark-theme &#{$modifier} {
        @content;
      }
    }
  } @else {
    @at-root .dark-theme & {
      @content;
    }
  }
}

.btn {
  background: transparent;

  &:hover {
    background: grey;
  }
  
  @include dark-theme {
    background: linear-gradient(cornflowerblue, rebeccapurple);
  }
  
  @include dark-theme(-enter) {
    background: cornflowerblue;
  }
  
  @include dark-theme(-enter-active, -exit) {
    background: rebeccapurple;
  }
}

输出

.btn {
  background: transparent;
}
.btn:hover {
  background: grey;
}
.dark-theme .btn {
  background: linear-gradient(cornflowerblue, rebeccapurple);
}

.dark-theme .btn-enter {
  background: cornflowerblue;
}

.dark-theme .btn-enter-active {
  background: rebeccapurple;
}

.dark-theme .btn-exit {
  background: rebeccapurple;
}

可以看到 @include dark-theme(-enter-active, -exit) 在多个修饰符的情况下生成了单独的重复内容。

要去掉重复我们可以直接拼接选择器。

@mixin dark-theme($modifiers...) {
  @if length($modifiers) > 0 {
    $selectors: ();
    @each $modifier in $modifiers {
      $selectors: append(
        $selectors,
        #{".dard-theme "}#{&}#{$modifier},
        comma
      );
    }
    @at-root #{$selectors} {
      @content;
    }
  } @else {
    @at-root .dark-theme & {
      @content;
    }
  }
}

输出

.btn {
  background: transparent;
}
.btn:hover {
  background: grey;
}
.dark-theme .btn {
  background: linear-gradient(cornflowerblue, rebeccapurple);
}

.dard-theme .btn-enter {
  background: cornflowerblue;
}

.dard-theme .btn-enter-active, .dard-theme .btn-exit {  background: rebeccapurple;}

抽象出通用 Mixin

最后我们将这个模式再进一步抽象出来,成为通用的 at-root mixin。

@mixin at-root($ancestor, $modifiers...) {
  @if length($modifiers) > 0 {
    $selectors: ();
    @each $modifier in $modifiers {
      $selectors: append(
        $selectors,
        #{$ancestor}#{" "}#{&}#{$modifier},
        comma
      );
    }
    @at-root #{$selectors} {
      @content;
    }
  } @else {
    @at-root #{$ancestor} & {
      @content;
    }
  }
}

现在 dark-theme 可以这么定义。

@mixin dark-theme($modifiers...) {
  @include at-root('.dark-mode', $modifiers...) {
    @content;
  }
}

最后

通过封装 mixin 我们可以方便地在规则内部直接引用祖先选择器定义规则,同时摆脱与祖先类型名的耦合,使到代码可以灵活应对变更。

评论没有加载,检查你的局域网

Cannot load comments. Check you network.

eat();

sleep();

code();

repeat();