はじめに
Sassの**@mixin
**はメンテナブルなCSS設計にも役立つ強力な機能です。
一方でゆるくも書けてしまうため、気をつけないと不格好なCSSが簡単に出力されてしまいます。
そこで@mixin
を自作するにあたって、知っておきたい(おきたかった)基本知識をまとめました。何かの参考にでもなれば幸いです。
前提条件
- ソースコードは**.scss**形式で記述しています
- Sass 3.4.4で動作を確認しています
- 使用しているModifierという単語は以下の通りです
BEMと呼ばれる命名規則で使われる、基本的なスタイル(セレクタ・class)に追加する形で装飾を調整するclassのこと
目次
-
@mixin
のおさらい- 基本的な使い方
-
@content
の使い方
- Modifierに無駄なスタイルを適用させたくない...
-
@content
を使ってみる - 制御用の引数を用意してみる
- 引数に配列(リスト)を使ってみる
- 引数に連想配列を使ってみる
-
- 番外編: 連想配列を入れ子にする
- 無駄なセレクタを増やしたくない...
-
@at-root
を使ってみる - &を使ってみる
-
- おまけ:
@debug
を使う - 終わりに
@mixin
のおさらい
基本的な使い方
- 引数によって細かくスタイルの設定ができます
-
,
区切りで複数の引数を渡すことができます - $arg: val という形で引数の初期値の設定ができます
@mixin hoge($color: #fff, $fSize: 16px) {
color: $color;
font-size: $fSize;
}
.text {
@include hoge(black, 20px);
}
.text2 {
// 引数は初期値のを使用
@include hoge();
}
.text {
color: black;
font-size: 20px;
}
.text2 {
color: #fff;
font-size: 16px;
}
値に,
が含まれるプロパティには()
で囲んで呼び出すか、引数に...
をつけます
@mixin hoge($family) {
font-family: $family;
}
.text {
@include hoge((Arial, Helvetica, sans-serif));
}
// もしくは
@mixin hoge($family...) {
font-family: $family;
}
.text {
@include hoge(Arial, Helvetica, sans-serif);
}
.text {
font-family: Arial, Helvetica, sans-serif;
}
引数名をつけて呼び出すことで、順序を気にしないで設定・好きなものだけ選択できます
@mixin hoge($color: #fff, $fSize: 16px) {
color: $color;
font-size: $fSize;
}
.text {
@include hoge($fSize: 20px, $color: black);
// ↓だとcolor: 20px; font-size: black;
// @include hoge(20px, black);
}
引数には配列(リスト)や連想配列(マップ・オブジェクト)を渡すこともできます。※後述
@content
の使い方
-
@mixin
の中で@content;
と記述するとコンテントブロックを渡すことができます - これにより1つのmixinで呼び出し側に合わせた処理が可能です
@mixin hoge() {
// js有効時には非表示にしたい
.js & {
display: none;
@content;
}
}
.div {
@include hoge();
}
.div2 {
@include hoge() {
// この中が@mixinの@contentに追加
font-weight: bold;
}
}
.js .div {
display: none;
}
.js .div2 {
display: none;
font-weight: bold;
}
Modifierに無駄なスタイルを適用させたくない...
本題(?)です。
例としてModifierに応じて文字色が変わるようなCSSを@mixin
で生成したいとします。
動作の説明しやすさを優先したため、これだとあまりmixinを使う意味は...というのは見逃してください x(
@mixin hoge($color: #fff, $fSize: 16px) {
color: $color;
font-size: $fSize;
font-weight: bold;
&:hover {
text-decoration: none;
}
}
.text {
@include hoge(black);
&.is-red {
@include hoge(red);
}
&.is-blue {
@include hoge(blue);
}
}
問題なく動作します。
が、当然CSSは以下のように出力されます。
.text {
color: black;
font-size: 16px;
font-weight: bold;
}
.text:hover {
text-decoration: none;
}
.text.is-red {
color: red;
font-size: 16px;
font-weight: bold;
}
.text.is-red:hover {
text-decoration: none;
}
.text.is-blue {
color: blue;
font-size: 16px;
font-weight: bold;
}
.text.is-blue:hover {
text-decoration: none;
}
Modifierは色だけ変えたいのですが、それ以外のスタイルがblockと同じように設定されてしまいました。
@content
を使ってみる
@content
を使ってModifierを制御してみます。
@mixin hoge($color: #fff) {
@content;
color: $color;
}
.text {
@include hoge(black) {
font-size: 16px;
font-weight: bold;
&:hover {
text-decoration: none;
}
}
&.is-red {
@include hoge(red);
}
&.is-blue {
@include hoge(blue);
}
}
.text {
font-size: 16px;
font-weight: bold;
color: black;
}
.text:hover {
text-decoration: none;
}
.text.is-red {
color: red;
}
.text.is-blue {
color: blue;
}
cssはキレイにはなりましたが、結局共通スタイルを@content;
で渡している(セレクタごとに直書きする必要がある)ため、mixinを使う意味が少なくなってしまいました。
制御用の引数を用意してみる
制御用の引数を用意し、引数の値をもとにModifierを制御してみます。
@mixin hoge($color: #fff, $fSize: 16px, $isBlock: false) {
// Modifierじゃなかったら
@if ($isBlock) {
font-size: $fSize;
font-weight: bold;
&:hover {
text-decoration: none;
}
}
color: $color;
}
.text {
@include hoge($color: black, $isBlock: true);
&.is-red {
@include hoge(red);
}
&.is-blue {
@include hoge(blue);
}
}
.text {
color: black;
font-size: 16px;
font-weight: bold;
}
.text:hover {
text-decoration: none;
}
.text.is-red {
color: red;
}
.text.is-blue {
color: blue;
}
いい感じに出力されました!
Modifierとして付与したいスタイルのみ設定されていることが分かります。
簡単なものであればこれでも良さそうです。
引数に配列(リスト)を使ってみる
少し考え方を変えて、配列を使ってModifierを調整してみます。
- Modifierとして使用するClassと設定する色をそれぞれ配列にします
- Classと色の順番は合わせます
@mixin hoge($baseColor: #fff, $fSize: 16px, $selectors:(), $colors:()) {
color: $baseColor;
font-size: $fSize;
font-weight: bold;
&:hover {
text-decoration: none;
}
// 引数の数を取得
$len: length($selectors);
@for $i from 1 through $len {
&#{nth($selectors, $i)} {
color: nth($colors, $i);
}
}
}
.text {
@include hoge(
$baseColor: black,
$selectors: ('.is-red', '.is-blue'),
$colors: (red, blue)
);
}
.text {
color: black;
font-size: 16px;
font-weight: bold;
}
.text:hover {
text-decoration: none;
}
.text.is-red {
color: red;
}
.text.is-blue {
color: blue;
}
いい感じに出力されました!
ただし、class名と色の順番を合わせるのが面倒かもしれません。
引数に連想配列を使ってみる
配列として渡していた2つのリストを key + value
としてまとめ、連想配列として渡します
@mixin hoge($baseColor: #fff, $fSize: 16px, $data:()) {
font-size: $fSize;
font-weight: bold;
color: $baseColor;
&:hover {
text-decoration: none;
}
@each $key, $val in $data {
&#{$key} {
color: $val;
}
}
}
.text {
$data: (
'.is-red' : red,
'.is-blue' : blue,
'.is-green': green
);
@include hoge($baseColor: black, $data: $data);
}
.text {
font-size: 16px;
font-weight: bold;
color: black;
}
.text:hover {
text-decoration: none;
}
.text.is-red {
color: red;
}
.text.is-blue {
color: blue;
}
.text.is-green {
color: green;
}
いい感じに出力されました!
配列を使っていた時と比べて、スマートになった気がします。
番外編: 連想配列を入れ子にする
連想配列の値には更に配列を指定することもできるので、入れ子にしてより複雑なことも可能です。
@mixin hoge($baseColor: #fff, $fSize: 16px, $data:()) {
font-size: $fSize;
font-weight: bold;
color: $baseColor;
&:hover {
text-decoration: none;
}
@each $selector, $style in $data {
&#{$selector} {
@each $prop, $val in $style {
#{$prop}: #{$val};
}
}
}
}
.text {
$data: (
'.is-red': (
color: red,
background-color: white
),
'.is-blue': (
color: blue,
background-color: green
),
'.is-green': (
color: green,
background-color: blue
)
);
@include hoge($baseColor: black, $data: $data);
}
.text {
font-size: 16px;
font-weight: bold;
color: black;
}
.text:hover {
text-decoration: none;
}
.text.is-red {
color: red;
background-color: white;
}
.text.is-blue {
color: blue;
background-color: green;
}
.text.is-green {
color: green;
background-color: blue;
}
呼び出し側で定義されるものが増えましたが、だいぶ汎用的になりました。
- mixinとしての汎用性と粒度のバランスが難しいかもしれません
- 場合によっては
@content;
を使った方がスマートかもしれません
無駄なセレクタを増やしたくない...
BEMのような命名規則でそのままElementを指定したり、シングルクラス式で書いたりすると無駄なセレクタが増えてしまうことがあります。
@mixin hoge($fSize: 16px) {
font-size: $fSize;
font-weight: bold;
&:hover {
text-decoration: none;
}
// ElementとそのModifierをmixin内に用意
// #{&} は呼び出すBlock名(ここでは'nagaiClassNameDayo')が入る
#{&}__head {
margin: 10px;
&.is-red {
color: red;
}
}
#{&}__body {
padding: 0;
&.is-blue {
color: blue;
}
}
// BlockのModifierをシングルクラス式で用意
#{&}--hide {
display: none;
}
}
.nagaiClassNameDayo {
@include hoge();
}
.nagaiClassNameDayo {
font-size: 16px;
font-weight: bold;
}
.nagaiClassNameDayo:hover {
text-decoration: none;
}
.nagaiClassNameDayo .nagaiClassNameDayo__head {
margin: 10px;
}
.nagaiClassNameDayo .nagaiClassNameDayo__head.is-red {
color: red;
}
.nagaiClassNameDayo .nagaiClassNameDayo__body {
padding: 0;
}
.nagaiClassNameDayo .nagaiClassNameDayo__body.is-blue {
color: blue;
}
.nagaiClassNameDayo .nagaiClassNameDayo--hide {
display: none;
}
.nagaiClassNameDayo
というclass名が全てに出現してしまいました。
命名規則でclass名の衝突を避けているはずなのでこれは無駄かもしれません。
@at-root
を使ってみる
@at-root
とセレクタの前に記述すると、そのセレクタのネスト状態を解除して出力してくれます
@mixin hoge($fSize: 16px) {
font-size: $fSize;
font-weight: bold;
&:hover {
text-decoration: none;
}
// @at-rootを宣言
@at-root #{&}__head {
margin: 10px;
&.is-red {
color: red;
}
}
// @at-rootを宣言
@at-root #{&}__body {
padding: 0;
&.is-blue {
color: blue;
}
}
// @at-rootを宣言
@at-root #{&}--hide {
display: none;
}
}
.nagaiClassNameDayo {
@include hoge();
}
.nagaiClassNameDayo {
font-size: 16px;
font-weight: bold;
}
.nagaiClassNameDayo:hover {
text-decoration: none;
}
.nagaiClassNameDayo__head {
margin: 10px;
}
.nagaiClassNameDayo__head.is-red {
color: red;
}
.nagaiClassNameDayo__body {
padding: 0;
}
.nagaiClassNameDayo__body.is-blue {
color: blue;
}
.nagaiClassNameDayo--hide {
display: none;
}
いい感じに出力されました!
#{&}
で親のclass名を保持しつつ、@at-root
を宣言したことでネストが解除されていることが分かります。
なお@at-root
はmixin内でなくても使用することができます。
.text {
display: block;
@at-root #{&}__head {
display: block;
}
@at-root #{&}__body {
display: block;
}
}
.text { display: block;}
.text__head { display: block;}
.text__body { display: block;}
※使い方によってはスタイルを破壊してしまうのでご注意ください!
.text {
@at-root * {
display: none;
}
}
&を使ってみる
sass 3.3より #{&}
だけでなく &
という書き方もできるようになりました。
&
を使うとSass側がElementやModifierと見なしてくれるのか、自動的にネストを解除して出力してくれます
@mixin hoge($fSize: 16px) {
font-size: $fSize;
font-weight: bold;
&:hover {
text-decoration: none;
}
// #{&} -> & へ
&__head {
margin: 10px;
&.is-red {
color: red;
}
}
&__body {
padding: 0;
&.is-blue {
color: blue;
}
}
&--hide {
display: none;
}
}
.nagaiClassNameDayo {
@include hoge();
}
@at-root
を使った時と同じように出力されます(コンパイル後のCSSは省略)。
今までのように#{&}
を使うとclass名が崩れるケースもあるため、&
を使う場合は&
をそのまま使った方が良いかもしれません。
おまけ: @debug
を使う
mixinで思うように出力されない!そんな時は@debug
が役に立つかもしれません。
JavaScriptでのconsole.log()
のようなもので、@debug XXX;
と記述するとその位置でのXXXの内容がコマンドラインに出力されます。
@mixin hoge() {
&__inner {
display: block;
}
@at-root #{&}__inner2 {
display: block;
}
}
.hoge, .fuga {
@include hoge();
}
.hoge__inner, .fuga__inner {
display: block;
}
.hoge, .fuga__inner2 {
display: block;
}
@at-root #{&}
で指定した方は思うようにコンパイルしてくれません。
何が起こっているのか@debug
で確認してみます。
@mixin hoge() {
@debug &;
@debug #{&};
@debug type-of(&); // 型を返す
@debug type-of(#{&}); // 型を返す
// 略...
}
xxx.scss:4 DEBUG: .hoge, .fuga
xxx.scss:5 DEBUG: .hoge, .fuga
xxx.scss:6 DEBUG: list
xxx.scss:7 DEBUG: string
一見同じ値が渡されていますが、実はデータタイプが異なっています。
つまり上記では ".hoge, .fuga"
という文字列がそのまま適用されてコンパイルされたと考えられます。「#{&}
を使うとclass名が崩れるケース」とはこのようなことですね。
終わりに
CSSプリプロセッサはWebデザイナーやコーダーといったノンプログラマーでも、触る機会は多いかと思います。
とりあえずで、色んなパターンのCSSが出力できるため、出力されたCSS自体を意識することは少ないかもしれません。
ですが、無駄なスタイルはパフォーマンス低下に繋がるため、ノンプログラマーでもなるべく気をつけたいところです。
※見返してみるとmixinを説明するのに使ったサンプルコードが雑すぎですね。もうちょっと実践的なものにすれば良かったかなと反省です