スタイルのまとまりを共通化する@extendと@mixin
フォントやボタンなど複数の箇所で共通するスタイルを適用したいとき、Sassでは@extend、@mixinの2つの機能が用意されています。
2つの機能は似ている印象がありますが、主な違いは@extendはセレクタ単位でスタイルを共通化し、@mixinはCSSプロパティ単位でスタイルを共通化します。
そのため、固定のスタイルを共通化したいときには@extend、必要に応じてCSSプロパティの値を変更しながらスタイルを共通化するなら@mixinといった使い分けをします。
例えば複数の箇所に適用したい以下のようなフォントのスタイルがあるとします。
style.css
.fontSet {
  font-family: noto-sans-cjk-jp, sans-serif;
  font-size: 1.0rem;
  font-weight: 400;
  line-height: 1.8em;
  color: #000;
}このスタイルのまとまりをそのまま複数箇所に適用するときは@extendを使います。
しかし、見出しや通常のテキストなどテキストの種類によってfont-sizeプロパティ、font-weightプロパティなど部分的にCSSプロパティの値を変えたいことがありますが、そのときは@mixinの方が適しています。
この2つの機能の特徴から違いをより具体的に挙げてみると以下のようになります。
- @extendはセレクタ単位でスタイルを共通化する、@mixinはCSSプロパティ単位でスタイルを共通化する
- @mixinは関数のように引数を使って個別のプロパティの値を変更することができる
- @media内と外のスコープが@extendはと@mixinで異なる
- @mixinは@contentを使ってメディアクエリの記述の共通化ができる
文字の説明だけでは分かりづらいため、以降はコードを交えながら解説していきます。
なお、SassについてはSCSS記法を使います。
@extendと@mixinの基本的な機能
まずは@extendと@mixinの基本的な使用シーンから、それぞれの特徴を見ていきます。
今回は例として以下のHTMLにあるh1要素とp要素に対して、sytle.cssに記載したスタイルを適用することを想定して進めます。(※CSSの書き方はあえて冗長になっています)
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>@extendと@mixinの違い</title>
  <link rel="stylesheet" href="css/style.css">
</head>
<body>
  <main>
    <h1>@extendと@mixinの違い</h1>
    <p>@extendと@mixinの違いを探るべく、我々はSassの奥地へと足を踏み入れた...</p>
  </main>
</body>
</html>style.css
body {
  font-size: 16px;
  background: #f7f7f7;
}
main h1 {
  font-family: noto-sans-cjk-jp, sans-serif;
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1.8em;
  color: #0083d9;
}
main p {
  font-family: noto-sans-cjk-jp, sans-serif;
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.8em;
  color: #000;
}@extend
まずは@extendを使って、上記と同じスタイルを適用するSassを書いていきます。
style.scss
body {
  font-size: 16px;
  background: #f7f7f7;
}
// 共通化したいスタイルを定義する
%fontSet {
  font-family: noto-sans-cjk-jp, sans-serif;
  font-size: 1.0rem;
  font-weight: 400;
  line-height: 1.8em;
  color: #000;
}
main {
  h1 {
    @extend %fontSet;
    font-size: 1.25rem;
    font-weight: 700;
    color: #0083d9;
  }
  p {
    @extend %fontSet;
    font-size: 0.875rem;
  }
}コンパイルして出力したCSSは以下のようになります。
style.css
body {
  font-size: 16px;
  background: #f7f7f7;
}
main h1, main p {
  font-family: noto-sans-cjk-jp, sans-serif;
  font-size: 1.0rem;
  font-weight: 400;
  line-height: 1.8em;
  color: #000;
}
main h1 {
  font-size: 1.25rem;
  font-weight: 700;
  color: #0083d9;
}
main p {
  font-size: 0.875rem;
}上記の例では共通化するスタイルを「拡張専用セレクタ」と呼ぶ%fontSetを使って定義し、h1要素とp要素に適用しています。
さらに個別の文字サイズや色などは必要に応じて個別に指定し、@extendで定義したスタイルを上書きする形になっています。
@mixin
続いて、@mixinを使って同様のスタイルを適用するSassを書いていきます。
style.scss
body {
  font-size: 16px;
  background: #f7f7f7;
}
// 共通化したいスタイルを定義する
@mixin fontSet( $font_size: 1.0rem, $font_weight: 400, $color: #000) {
  font-family: noto-sans-cjk-jp, sans-serif;
  font-size: $font_size;
  font-weight: $font_weight;
  line-height: 1.8em;
  color: $color;
}
main {
  h1 {
    @include fontSet( 1.25rem, 700, #0083d9);
  }
  p {
    @include fontSet( 0.875rem);
  }
}コンパイルして出力したCSSは以下のようになります。
style.css
body {
  font-size: 16px;
  background: #f7f7f7;
}
main h1 {
  font-family: noto-sans-cjk-jp, sans-serif;
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1.8em;
  color: #0083d9;
}
main p {
  font-family: noto-sans-cjk-jp, sans-serif;
  font-size: 0.875rem;
  font-weight: 400;
  line-height: 1.8em;
  color: #000;
}@mixinは引数を使ってCSSプロパティの値を受け渡しできるため、Sassの記述は@extendよりシンプルになっていることが分かります。
一方で、出力されたCSSを見ると@includeした箇所にスタイルを全て書き出しているため、冗長な書き方になっていることが分かります。
以上のように、スタイルを共通化するアプローチがそれぞれ異なります。
@mediaでのスコープの違い
@extendと@mixinはどちらもスコープを持ちますが、特にメディアクエリで使用する@mediaのスコープでは大きな違いがあります。
@extend
@extendは@mediaの外で定義したスタイルを@media内で使うことはできません。
@media内でもスタイルの共通化をしたいときは@media内で改めて定義し直すことで使うことができます。
style.scss
// 共通化したいスタイルを定義する
%bgColor {
  background: #f7f7f7;
}
body {
  @extend %bgColor;
}
@media screen and (max-width: 980px) {
  // @media内用の共通化したいスタイルを定義する
  %bgColor2 {
    background: #eee;
  }
  body {
    @extend %bgColor2; // @mediaの外で定義した%bgColorは使えない
  }
}コンパイルして出力したCSSは以下のようになります。
style.css
body {
  background: #f7f7f7;
}
body {
  font-size: 16px;
}
@media screen and (max-width: 980px) {
  body {
    background: #eee;
  }
}@mixin
@mixinは@mediaの外で定義したスタイルでも@media内で使うことができます。
以下の例では冒頭で定義したbgColorSetを@media内からも呼び出して使っています。
style.scss
// 共通化したいスタイルを定義する
@mixin bgColorSet($background_color:#f7f7f7) {
  background: $background_color;
}
body {
  font-size: 16px;
  @include bgColorSet;
}
@media screen and (max-width: 980px) {
  body {
    @include bgColorSet(#eee); // @mediaの外で定義したbgColorSetを使える
  }
}コンパイルして出力したCSSは以下のようになります。
style.css
body {
  font-size: 16px;
  background: #f7f7f7;
}
@media screen and (max-width: 980px) {
  body {
    background: #eee;
  }
}@contentを使ってメディアクエリの記述を共通化する
@contentは@mixinで使うことができる機能ですが、この機能を使うとメディアクエリの記述を共通化することができます。
style.scss
// 共通化したいスタイルを定義する
@mixin mediaQuerySet($max_width: 980px) {
  @media screen and (max-width: $max_width) {
    @content;
  }
}
#content1 {
  width: 100%;
  max-width: 1200px;
  @include mediaQuerySet {
    padding: 0 3%;
    width: 94%;
  }
  @include mediaQuerySet(768px) {
    padding: 0 5%;
    width: 90%;
  }
}
#content2 {
  width: 100%;
  max-width: 1200px;
  @include mediaQuerySet {
    padding: 0 3%;
    width: 94%;
  }
  @include mediaQuerySet(768px) {
    padding: 0 5%;
    width: 90%;
  }
}コンパイルして出力したCSSは以下のようになります。
style.css
#content1 {
  width: 100%;
  max-width: 1200px;
}
@media screen and (max-width: 980px) {
  #content1 {
    padding: 0 3%;
    width: 94%;
  }
}
@media screen and (max-width: 768px) {
  #content1 {
    padding: 0 5%;
    width: 90%;
  }
}
#content2 {
  width: 100%;
  max-width: 1200px;
}
@media screen and (max-width: 980px) {
  #content2 {
    padding: 0 3%;
    width: 94%;
  }
}
@media screen and (max-width: 768px) {
  #content2 {
    padding: 0 5%;
    width: 90%;
  }
}@extendと@mixinの違いをコードを使いながら見てきました。
@mixinの方が機能が多く柔軟性がありますが、@extendの方がコンパイル後のCSSはまとまるため、実際には適用するスタイルに応じて使い分けながらどちらも使うというケースが多いです。