【Sass】特定の親セレクタの状態を変更するmixin ネスト外にも対応

Sassでネストされたセレクタの深い位置から上のセレクタがホバーした時などのcssを書くときちょっと面倒だったりしませんか?

今回の記事ではネストから上のセレクタの状態を楽に変更できるmixinを作成してみたのでご紹介します!

BEMで書いている場合はネストをあまりせずに独立してスタイルを書くことが多いと思いますがそれにも対応しています。

特定の親セレクタの状態を変更するmixin

紹介するmixinはSass直前の親セレクタを取得する関数と組み合わせて使用します。

Sass直前の親セレクタを取得する関数については以下の記事で解説しています。

デモ

各カードの色とホバー効果のスタイルにmixinを使用しています。

mixinの紹介

以下をコピペでOK!

//指定したclassがネスト内にあるかチェック
@function is-inside($target) {
  @return if("#{selector-replace(&, $target, ".replaced")}" != "#{&}", true, false);
}

//ネストされたセレクターの直前の親セレクターを取得する関数
//.hoge .hoge__box だと .hoge__box を取得
@function parent($value) {
  $selector: $value;
  $array: "";
  $space: if(str-index($selector, " "), str-index($selector, " "), 0);//セレクターのスペースの位置を保存※スペースがなければ0

  //セレクターにスペースがあれば繰り返し
  @while $space > 0 {
    $selector: str-insert($selector, ',', $space);//スペースの位置に,(カンマ)を挿入
    $array: selector-parse($selector);//配列に変換
    //  // $l: length($array);
    $selector: #{nth($array, length($array))};//配列の最後を取得
    $space: if(str-index($selector, " "), str-index($selector, " "), 0);//セレクターのスペースの位置を保存※スペースがなければ0
  }
  @return "#{$selector}";
}

@mixin state ($target, $state) {
  //ネストの外か内を判定
  @if is-inside($target) {
    @at-root #{selector-replace(&, $target, $target + $state)} {
      @content
    }
  } @else {
    @if $target == parent(#{&})  {
      @at-root #{selector-replace(&, parent(#{&}), $target + $state)} {
        @content
      }
    } @else {
      @at-root #{selector-replace(&, parent(#{&}), $target + $state + " " + parent(#{&}))} {
        @content
      }
    }
  }
}

読み込み

@importで読み込みます。
ファイル名は_state.scssとします。

@import "path/to/state";

このmixinはSassの&で対応しきれない箇所に使用します。

.card {
  .card__inner {
    .card__title {

    }
  }
}

.card__innerhoverしたときの.card__titleのスタイルを書きたい場合は&では書くことができません。
このような場合に今回のmixinを使用します。

使い方

@include state("状態を変えたいセレクター", "付与したい状態(classやhoverなど)") {
    //ここにスタイルを記述
}

HTML

デモで使用しているカードタイプのHTMLです

<div class="card -green">
    <a href="#" class="card__inner">
        <h3 class="card__title">タイトルです</h3>
        <figure class="card__image">
            <img src="https://dl.dropbox.com/s/5l3d94y7s7es75d/sample04.jpg?dl=0" alt="カード画像" class="card__image-content">
        </figure>
        <div class="card__detail">
            <p class="card__text">サンプルテキストさんぷるてきすとサンプルテキストさんぷるてきすと</p>
        </div>
    </a>
</div>

SCSS

デモで使用しているSCSSの抜粋
ネストしている場合とネストしていない場合両方を紹介

ネストしている
.card {
  //.cardに.-greenが付与されている場合
  .card__inner {
    background: white;
    @include state(".card", ".-green") {
      background: green;
    }
    //.card__innerをhoverした時
    .card__title {
      color: #333;
      @include state(".card__inner", ":hover") {
        color: #fff;
      }
    } //card__title
    .card__image-content {
      transition: transform 0.2s;
      @include state(".card__inner", ":hover") {
        transform: scale(1.2);
      }
    } //card__image-content
  } //card__inner
} //card
ネストしていない
//.cardに.-greenが付与されている場合
.card__inner {
  background: white;
  @include state(".card", ".-green") {
    background: green;
  }
}

//.card__innerをhoverした時
.card__title {
  color: #333;
  @include state(".card__inner", ":hover") {
    color: #fff;
  }
}
.card__image-content {
  transition: transform .2s;
  @include state(".card__inner", ":hover") {
    transform: scale(1.2);
  }
}

コンパイル結果

ネストしている
/* .cardに.-greenが付与されている場合 */
.card .card__inner {
  background: white;
}
.card.-green .card__inner {
  background: green;
}

/* .card__innerをhoverした時 */
.card .card__inner .card__title {
  color: #333;
}
.card .card__inner:hover .card__title {
  color: #fff;
}

.card .card__inner .card__image-content {
  transition: transform .2s;
}
.card .card__inner:hover .card__image-content {
  transform: scale(1.2);
}
ネストしていない
/* .cardに.-greenが付与されている場合 */
.card__inner {
  background: white;
}
.card.-green .card__inner {
  background: green;
}

/* .card__innerをhoverした時 */
.card__title {
  color: #333;
}
.card__inner:hover .card__title {
  color: #fff;
}

.card__image-content {
  transition: transform .2s;
}
.card__inner:hover .card__image-content {
  transform: scale(1.2);
}

このように簡単に状態を変化させたcssを書くことができます。

以前紹介したreplace mixinでも同じことはできるが...

以前紹介したreplace mixinでも同じことはできるのですが、可読性はこちらのほうが上です。

//.card__innerをhoverした時
.card__title {
  color: #333;
  //今回紹介したstate mixin
  @include state(".card__inner", ":hover") {
    color: #fff;
  }
  //以前紹介したreplace mixin
  @include replace(".card__title", ".card__inner:hover .card__title") {
    color: #fff;
  }
}

このように明らかに今回紹介したmixinのほうが可読性は高いです。

最後に

Sassはmixinを駆使してらくできるのはいいですよね。
しかしオレオレmixinを書きすぎるのは業務で引き継ぎのときに困りますのでほどほどにするのが良いかと思います。