背景
-
SP 画面でテーブルレイアウトが崩れていたので SCSS で修正することになった
-
修正コードを書く中で
@include media-breakpoint-down(tab)という Bootstrap 5 互換の mixin に出会ったが、そもそも@mixin/@include/@contentの理解が曖昧だった。せっかくなので SASS の基本からまとめ直すことにした
SASS / SCSS の基本
SASS は CSS の拡張言語で、ビルド時に 素の CSS に変換される。CSS にはない変数や mixin、ネスト記法といった機能が使える
SCSS と SASS の違い
- SCSS(
.scss)は SASS の CSS 風記法。{ }ベースで書ける - SASS(
.sass)はインデントベースの記法 - 多くのプロジェクトで「SASS」と呼ばれているのは実は SCSS
実用上は SCSS を採用するケースが多い。CSS と互換性があるので学習コストが低い
ネスト記法
.card {
padding: 16px;
.title {
font-size: 20px;
}
&:hover {
background-color: #f5f5f5;
}
}
ビルド後:
.card { padding: 16px; }
.card .title { font-size: 20px; }
.card:hover { background-color: #f5f5f5; }
& は親セレクタを参照する記法で、&:hover のように擬似クラスと組み合わせるときに使う
変数
$xxx 形式で値に名前を付けて再利用できる仕組み
定義と参照
$primary-color: #3498db;
$base-padding: 16px;
.button {
background-color: $primary-color;
padding: $base-padding;
}
.alert {
border: 1px solid $primary-color;
}
ビルド後:
.button { background-color: #3498db; padding: 16px; }
.alert { border: 1px solid #3498db; }
どんなときに使うか
- 色・サイズ・余白などの定数: ブランドカラーや基本フォントサイズなど、プロジェクト全体で統一したい値
-
計算の元になる値:
$base-padding * 2のように演算できる -
意味付け:
#3498dbより$primary-colorの方が読み手に意図が伝わる
Bootstrap も $grid-breakpoints や $primary などを変数で持っており、テーマカスタマイズはこれらの変数を上書きすることで行う
mixin と @include
スタイルのテンプレートを定義して再利用する仕組み
-
@mixin 名前 { ... }= スタイルテンプレートの 定義 -
@include 名前;= テンプレートを 呼び出し
@mixin で定義する
@mixin button-base {
display: inline-block;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
@include で呼び出す
.primary-button {
@include button-base;
background-color: blue;
color: white;
}
.secondary-button {
@include button-base;
background-color: gray;
}
ビルド後、button-base の中身が各セレクタに展開される
.primary-button {
display: inline-block;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
background-color: blue;
color: white;
}
.secondary-button {
display: inline-block;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
background-color: gray;
}
引数を取る mixin
@mixin button-variant($bg, $color) {
background-color: $bg;
color: $color;
}
.primary-button {
@include button-variant(blue, white);
}
引数を取れることで、似ているが少しだけ違うパターンを汎用化できる
@content — 呼び出し側のスタイルを差し込む
mixin の中で「呼び出し側の { } の中身」を展開する仕組み。これがレスポンシブ mixin の鍵
仕組み
@mixin sp-only {
@media (max-width: 767.98px) {
@content; // ← 呼び出し側のスタイルがここに入る
}
}
// 使う側
.title {
font-size: 32px;
@include sp-only {
font-size: 20px; // この行が @content の位置に展開される
}
}
ビルド後:
.title { font-size: 32px; }
@media (max-width: 767.98px) {
.title { font-size: 20px; }
}
@include の後に { } を付けて中身を渡すと、mixin 内の @content の位置に展開される
@content が活きる主なケース
// 1. レスポンシブ
@mixin sp-only {
@media (max-width: 767.98px) { @content; }
}
// 2. 擬似クラス
@mixin on-hover {
&:hover { @content; }
}
// 3. 親セレクタ条件
@mixin within-modal {
.modal & { @content; }
}
使い方:
.btn {
color: black;
@include on-hover {
color: blue;
}
}
「条件(メディアクエリ・擬似クラス・親セレクタ)」と「その中身のスタイル」を分離して書けるのが @content の本質
レスポンシブ mixin の実例:media-breakpoint-down
Bootstrap 5 互換の mixin で、指定したブレークポイント名 未満 の viewport にスタイルを適用する
media-breakpoint-down(tab) の正体
@include media-breakpoint-down(tab) {
// スタイル
}
これは内部的に以下のように展開される
@media (max-width: 1023.98px) {
// スタイル
}
仕組み:
-
$grid-breakpointsというマップ(連想配列)からブレークポイント名に対応する値を引く - そこから
0.02px引いた値をmax-widthとして@mediaクエリを生成 -
0.02引くのは Bootstrap の慣習で、min-width側との境界が重ならんように隙間を作るため
このプロジェクトでは独自に tab: 1024px を $grid-breakpoints に追加して、SP/PC 境界として運用している
Bootstrap 5 の mixin
| mixin | 意味 | 出力例 (tab: 1024px の場合) |
|---|---|---|
media-breakpoint-up(tab) |
以上 (≥1024px) | @media (min-width: 1024px) |
media-breakpoint-down(tab) |
未満 (<1024px) | @media (max-width: 1023.98px) |
media-breakpoint-only(tab) |
該当区間のみ | @media (min-width and max-width) |
media-breakpoint-between(sm, tab) |
範囲 | @media (min-width and max-width) |
PC ファーストとモバイルファースト
-
PC ファースト: PC スタイルを基底とし、
down(tab)で SP に上書き -
モバイルファースト: SP スタイルを基底とし、
up(tab)で PC に上書き
Bootstrap 公式はモバイルファースト推奨だが、業務系 SaaS は PC が主役のケースが多いので、PC ファーストもアリな選択肢
mixin はどんなケースで使うか
mixin が威力を発揮するのは、「条件(メディアクエリ・擬似クラス・親セレクタなど)」と「その中身のスタイル」を分離したいとき
よくあるケース
| ケース | 例 | 効果 |
|---|---|---|
| レスポンシブ | @include sp-only { ... } |
メディアクエリの記述を 1 箇所に集約 |
| 擬似クラスの統一 | @include on-hover { ... } |
hover の挙動をプロジェクト全体で揃える |
| 親セレクタ条件 | @include within-modal { ... } |
「モーダル内では」のような文脈を簡潔に |
| ボタン・カードなどのコンポーネント | @include button-base |
共通スタイルを使い回す |
| ブラウザ別の hack | @include only-safari { ... } |
ベンダープレフィックスを隠蔽 |
まとめ
- SASS は CSS の拡張言語。変数・mixin・ネスト記法でスタイルの DRY を実現する
-
@mixinでテンプレートを定義し、@includeで呼び出す -
@contentを使うと「呼び出し側の{ }の中身」を mixin 内に差し込める。これがレスポンシブ mixin の鍵 -
media-breakpoint-down(tab)は Bootstrap 5 互換の mixin で、内部的には@media (max-width: 1023.98px)を生成している - mixin は「条件とスタイルを分離する」用途で威力を発揮する。同じパターンが 2 回以上出たら mixin 化を検討
感想
-
@include media-breakpoint-down(tab)を見て「謎の関数呼び出し」に見えてたけど、@mixinと@contentの仕組みを理解すると「ただのメディアクエリの薄いラッパー」だった - 久しぶりにcss周り調査したな