Sassのネスト内で、親の親要素などを参照する方法

  • CSS

Sassのネストは便利ですが、親要素次第でネストが分かれ、読みづらくなってしまうことがあります。
そうならないように構築していくのがベストなのですが、(局所的な対応で)そうなってしまうことが多いと思います。

今回は「.foo」から派生する「.bar」のネストの中で、以下の3パターンに対応した方法をご紹介します。

  1. bodyのクラスを参照する
  2. 親要素との間に、別の親要素を入れて参照する
  3. 親子関係のないセレクタに、別の文字列を入れて参照する

元のファイル

.foo{

  .bar{
    background-color: #000;
  }
  
}

/* 結果
.foo .bar {
  background-color: #000
}
*/

bodyのクラスを参照する

bodyに付与された「.page01」を参照して、「.page01 .foo .bar」にスタイルを当てる場合です。
実は祖先要素から参照していくのは、非常に簡単です。

「.page01」からネストにしていくと、このような書き方になりますが…。

.foo{

  .bar{
    background-color: #000;
  }

}

.page01{

  .foo{

    .bar{
      background-color: #ccc;
    }

  }

}

/* 結果
.foo .bar {
  background-color: #000;
}

.page01 .foo .bar {
  background-color: #ccc;
}

*/

「.page01 &」とすることで、「.foo」のさらに親の「.page01」から参照できます。
この場合での「&」は、「.foo .bar」を返しています。

.foo{

  .bar{
    background-color: #000;

    .page01 &{
      background-color: #ccc;
    }

  }

}

/* 結果
.foo .bar {
  background-color: #000;
}

.page01 .foo .bar {
  background-color: #ccc;
}

*/

「page02」以降で、あまりにも多くの要素に変更があると読みづらくなってしまいますが、
背景画像など単一のコンテンツに@forや@eachでスタイルを当てる場合、スッキリするかと思います。

@at-rootを使う

@at-rootを使用しても同じことができます。
このディレクティブを用いると、現在のネストを解除して出力されます。

.foo{

  .bar{
    background-color: #000;

    @at-root .page01 &{
      background-color: #ccc;
    }

  }

}

/* 結果
.foo .bar {
  background-color: #000;
}

.page01 .foo .bar {
  background-color: #ccc;
}
*/

@at-rootを単体で使うことはあまりありませんが、
@contentを組み合わせ、汎用的な@mixinを作る際に役立ちます。

@mixin elm($element){

  @at-root &__#{$element}{
    @content;
  }

}

.foo{

  @include elm(bar){
    background-color: #ccc;
  }

}

/* 結果
.foo__bar {
  background-color: #ccc;
}
*/

親要素との間に、別の親要素を入れて参照する

「.foo .bar」の間に「.baz」を入れて「.foo .baz .bar」を参照するようにしてみます。
&を用いると、「.foo .bar」が出力されるので、この間に「.baz」が入るようにすれば良いと思います。

以下の記述ですと、&が「.foo .bar」を返すので、
cssで「.foo .baz .foo .bar」を出力してしまいます。

.foo{

  .bar{
    background-color: #000;

    .foo .baz &{
      background-color: #666;
    }

  }

}

/* 結果(×)
.foo .bar {
  background-color: #000;
}

.foo .baz .foo .bar {
  background-color: #666;
}
*/

そこで、&の内容を、Sassの関数で書き換えます。
selector-replace(要素,書き換え前のセレクタ,書き換え後のセレクタ)関数を用いることで、参照セレクタを書き換えることができます。

まず、現在のセレクタの内容を書き換えたいので、第1引数に&を渡します。
次に、「.foo .bar」の「.foo」の部分を「.foo .baz 」に書き換えたいので、
第2引数には「.foo」を、第3引数には「.foo .baz」を入れます。

あらかじめ「.foo」の部分を変数として入れておくと、selector-replace関数で使い回すことができます。

上記をまとめると、このようになります。

.foo{
  $parent:&; //「.foo」が入る

  .bar{
    background-color: #000;

    //「.foo .bar」を「.foo .baz .bar」に書き換える
    @at-root #{selector-replace(&,#{$parent},#{$parent+' .baz'})}{
      background-color: #666;
    }

  }

}

/* 結果
.foo .bar {
  background-color: #000;
}

.foo .baz .bar {
  background-color: #666;
}
*/

複雑なので、@mixinにします。

@mixin replace_parent($parent,$elm){
  
  @at-root #{selector-replace(&,#{$parent},#{$parent+$elm})}{
  @content;
  }
  
}

.foo{
  $parent:&;

  .bar{
    background-color: #000;

    @include replace_parent($parent,' .baz'){
      background-color: #666;
    }

  }

}

/* 結果

.foo .bar {
  background-color: #000
}

.foo .baz .bar {
  background-color: #666
}

*/

「foo.baz .bar」に当てたい場合は、置き換え先の文字’ .baz’の半角スペースを消すだけです。

親子関係のないセレクタに、別の文字列を入れて参照する

実用性がなさそうですが、「.foo__bar」と、「.foo–baz__bar」の時で、スタイルを変える方法です。
先ほどの例との違いは、親子関係がなくなることです。

selector-replace関数は、あくまでも参照するセレクタを書き換えるもので、文字列そのものを変更することはできません。
したがって親子関係のないセレクタにおいて、先ほどと同じ記述をしても機能しません。

.foo{
  $parent:&;

  &__bar{
    background-color: #000;

    @at-root #{selector-replace(&,#{$parent},#{$parent+'--baz'})}{
      background-color: #666;
    }

  }

}

/* 結果(×)
.foo__bar {
  background-color: #000;
}

.foo__bar {
  background-color: #666;
}
*/

よって、文字を書き換える関数を用意する必要があります。
今回は「css-tricks」のstr-replace-functionをお借りしました。

// https://css-tricks.com/snippets/sass/str-replace-function/
/// Replace `$search` with `$replace` in `$string`
/// @author Hugo Giraudel
/// @param {String} $string - Initial string
/// @param {String} $search - Substring to replace
/// @param {String} $replace ('') - New value
/// @return {String} - Updated string
@function str-replace($string, $search, $replace: '') {
  $index: str-index($string, $search);
  
  @if $index {
    @return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
  }
  
  @return $string;
}

.foo{
  $parent:&;

  &__bar{
    background-color: #000;

    @at-root #{$parent}--baz#{str-replace(#{&},#{$parent})}{
      background-color: #666;
    }

  }

}

/* 結果
.foo__bar {
  background-color: #000;
}

.foo--baz__bar {
  background-color: #666;
}
*/

やはり複雑なので、@mixinにします。

@mixin parent_modifier($parent,$modifier){
  @at-root #{$parent}#{$modifier}#{str-replace(#{&},#{$parent})}{
  @content;
  }
}

.foo{
  $parent:&;

  &__bar{
    background-color: #000;

    @include parent_modifier($parent,--baz){
      background-color: #666;
    }

  }

}

/* 結果
.foo__bar {
  background-color: #000;
}

.foo--baz__bar {
  background-color: #666;
}
*/

可読性を著しく損なうので、そのまま使用するのは現実的でないのですが、
何かの参考になれば幸いです。

Writer

hys

よろしくお願いします。

Page Topへ