制作で使い回す Sass mixin 一覧(ホバー / ボタン / アイコン / reduced-motion)

  • Sass
  • CSS
  • アクセシビリティ

よしあきは、Vite + EJS + Sass で静的サイトを組み立てる制作テンプレートを日々使っている。その中で、ホバー演出やボタン装飾を mixin に切り出して再利用 している。案件ごとに CSS を書き直すより、同じ見え方・同じアクセシビリティ配慮 を短い @include で呼べるほうが、レビューと移植(WordPress テーマへ持っていくときなど)が楽になる。

本記事は、その mixin 群を 用途別のカタログ としてまとめたもの。コードの正本は制作テンプレート側の src/assets/sass/global/mixins/ に置いてあり、各ファイル冒頭にも使い方コメントがある。目で確認できるデモ静的テンプレのデモ一覧 以下(ホバー系は ボタンテキストカード)。各デモには @include の記述例も載せている。

読み方

  1. 例 — hover-scale-img
  2. 前提:読み込み方と共通変数
  3. mixin 全体に共通する設計
  4. 一覧(用途別)
  5. 組み合わせ例
  6. reduced-motion
  7. 運用上の注意

例 — hover-scale-img

カードリンクの サムネだけ ホバーで拡大させる。マークアップは figure の直下に img がある前提。

<a href="…" class="p-card__link">
  <figure class="p-card__thumb">
    <img src="…" alt="…">
  </figure>

</a>

mixin を使わないと、コンポーネント SCSS 側で overflow・transition・any-hover・focus-visible・リンク opacity の打ち消し まで毎回書く。

入れ子で書く例:

.p-card__link {
  figure {
    overflow: hidden;
  }
  img {
    transition: scale 0.3s ease-in-out;
  }
  @media (any-hover: hover) {
    &:hover {
      opacity: 1;
    }
    &:hover img {
      scale: 1.1;
    }
  }
  &:focus-visible {
    opacity: 1;
  }
  &:focus-visible img {
    scale: 1.1;
  }
}

よしあきの SCSS では 入れ子を使わず、セレクタを平坦に書く。出力される CSS は同じ。

.p-card__link figure {
  overflow: hidden;
}

.p-card__link img {
  transition: scale 0.3s ease-in-out;
}

@media (any-hover: hover) {
  .p-card__link:hover {
    opacity: 1;
  }

  .p-card__link:hover img {
    scale: 1.1;
  }
}

.p-card__link:focus-visible {
  opacity: 1;
}

.p-card__link:focus-visible img {
  scale: 1.1;
}

どちらにしても行数は多い。mixin に切り出すと、読み込み一行 + @include 一行 で同じ契約に寄せられる。

@use "../global" as *;

.p-card__link {
  @include hover-scale-img();
}

拡大率だけ変えたいときは @include hover-scale-img(1.2); のように引数を渡す。詳細は 一覧 — シンプルなホバー を参照。動作例: ホバーエフェクト(カード)(画像拡大・上へ・シャドウ・明るさ)。

本記事の残りは、この 「長い CSS を短い @include に置き換える」 前提で、mixin ごとの引数と注意点をカタログ化している。


前提:読み込み方と共通変数

コンポーネント SCSS では、次の一行で mixin を使える。

@use "../global" as *;

エントリの style.scss でも同様に @use './global' as *; している。global 配下で mixin を @forward しているため、個別ファイルを @use し直す必要はない

ホバー系 mixin の多くは、トランジション時間に --hover-transition:root0.3s ease-in-out)を参照する。サイト全体でホバーの「速さ」を揃えたいときは、この CSS 変数だけ触ればよい。

ボタン系(背景スライド・リップルなど)は、色の切り替えに --button-bg-hover--button-text-color-hover などのカスタムプロパティを前提にしている。見た目はコンポーネント側の :root / 修飾クラスで決め、mixin は「動き」だけ担当する形だ。


mixin 全体に共通する設計

制作テンプレのホバー mixin には、次の方針が繰り返し現れる。

@media (any-hover: hover) でホバーを限定する

タッチ主体の端末では :hover が「張り付く」ことがある。ポインタでホバーできる環境だけ :hover スタイルを当てるため、any-hover: hover を使っている。

:focus-visible をホバーと揃える

キーボード操作でも、マウスホバーと同じフィードバックが出るように、多くの mixin が :focus-visible に同じ見た目を書いている。

リンクの hover-opacity を打ち消す

base/_base.scss では、サイト内リンク ahover-opacity(既定で不透明度 0.7)が当たっている。ボタンやナビ項目に別のホバー mixin を足すと、:hoveropacity: 1 を戻す処理が mixin 側に入っている。これを忘れると、意図した色変化のあとリンク全体が薄く見える。

擬似要素の競合に注意

::before / ::after を mixin 内で使うもの(背景スライド、リップル、アイコンなど)は、同じセレクタで別用途の擬似要素を置かない前提。コメントにも「他用途と競合させない」と書いてある。


一覧(用途別)

シンプルなホバー

mixin概要主な引数
hover-opacity不透明度を変える$opacity(既定 0.7)
hover-brightnessfilter: brightness()$brightness(既定 1.1)
hover-scale要素全体を拡大$scale, $duration, $easing
hover-scale-img子の img だけ拡大(親 figureoverflow: hidden同上
hover-uptranslateY で浮かせる$translateY(既定 -4px)
hover-shadowbox-shadow と軽い上方向移動$translate-y(既定 -2px)

デモ: ホバーエフェクト(カード)

.p-card__link {
  @include hover-up();
}

.p-card__thumb {
  @include hover-scale-img();
}

背景・ボタン向け

mixin概要擬似要素
hover-bg背景色・文字色を CSS 変数で切替なし
hover-bg-position-slidebackground-position をスライドなし
hover-bg-slide-before左から右へ背景が scaleX で伸びる::before
hover-ripple-after中心から円が広がるリップル風::after

hover-bg-slide-before は、ホバー完了後に本体の background-color を透明にする $clear-bg-delay も取れる。元の背景色が滲むときの逃げ道だ。

デモ: ホバーエフェクト(ボタン)(背景・スケール・スライド・シャドウ・リップル・アイコン・グラデーション)

テキスト装飾

mixin概要マークアップ注意
hover-text-underlinetext-decoration-color を透明→表示特になし
hover-text-underline-span-before直下 span::before で左→右の下線テキストを直下 span で包む
text-clipbackground-clip: text でグラデ文字背景画像引数可

ヘッダーナビなどでは hover-text-underline-span-before を、デモページ(ホバーエフェクト(テキスト))では両方の下線 mixin を並べて比較できるようにしている。

アイコン付きボタン

mixin概要
button-icon-aftermask で SVG アイコンを ::after に表示
button-icon-after-nudge上記と併用。ホバーでアイコンを横にずらす
.c-button {
  @include button-icon-after(url("../images/common/icon_arrow_right.svg"));
  @include button-icon-after-nudge;
}

button-icon-after-nudge 単体では ::after を定義しない。必ず button-icon-after とセットにする。


組み合わせ例

ホバーエフェクト(ボタン) では、セクションごとに mixin の SCSS 記述例とプレビューを並べている。カタログ上の対応は次のとおり。

  • 背景色切替 → hover-bg
  • 拡大 → hover-scale
  • 背景スライド → hover-bg-slide-before
  • 影+浮き → hover-shadow
  • リップル → hover-ripple-after
  • 右矢印アイコン → button-icon-after + button-icon-after-nudge

カードリンクでは hover-scale-img(サムネ拡大)と hover-up(全体を少し持ち上げ) を別要素に分けて使う例もある(ホバーエフェクト(カード))。同じ mixin を1セレクタに二重適用するより、役割ごとに DOM を分けるほうが読みやすい。


reduced-motion

OS の「視差効果を減らす」「動きを減らす」設定向けに reduced-motion mixin がある。prefers-reduced-motion: reduce のとき transition: none を当て、必要なら @content で打ち消しを足せる。

.p-header {
  transition: background-color 0.3s ease-in-out;
  @include reduced-motion();
}

[data-fadein] {
  opacity: 0;
  transform: translateY(2.5rem);
  transition: opacity 1.4s ..., transform 0.6s ...;

  @include reduced-motion {
    opacity: 1 !important;
    transform: none !important;
  }
}

スクロール連動のフェードイン([data-fadein])では、初期状態の transform / opacity を reduce 時に即時表示へ戻すのが典型パターン(スクロールアニメーション デモでも @include reduced-motion の例あり)。Splide デモのページネーション進行アニメーションのように、「動き OK のときだけ keyframes を動かす」逆条件は、現状 mixin 化していない(prefers-reduced-motion: no-preference の直書きが残る)。

JavaScript 側(スムーススクロールなど)でも matchMedia('(prefers-reduced-motion: reduce)') で分岐している。CSS mixin と JS の判定は別レイヤーとして両方必要になる。


運用上の注意

案件に持っていくとき — WordPress テーマなど別スタックへ移植するときは、mixin ファイルごと @forward 一覧を揃え、@use "../global" as * のパスだけ合わせればよい。hover 系は DOM 契約(span で包む下線、アイコン用 ::after)までセットで移す。

kiso / reset との重複 — mixin はホバーと装飾に特化している。margin リセットや line-height など土台 CSS の再宣言は別ルール(コンポーネント SCSS では kiso と同値を書かない)で守る。

デモと本番 — テンプレートにはデモ用コンポーネントが多く、案件着手時に npm run init でデモ一式を削除する運用もある。型として残したい mixin は global/mixins/ に置いたまま、デモ SCSS だけ消えるイメージだ。デモの入口は デモ一覧


まとめ

制作テンプレの Sass mixin は、大きく (1) シンプルなホバー (2) ボタン・背景演出 (3) テキスト・アイコン (4) reduced-motion に分けられる。どれも any-hover + focus-visible と、リンク既定スタイルとの opacity: 1 調整 が共通底面にある。

新しいインタラクションを足す前に、既存 mixin で足りないかを見る。足りなければ 擬似要素の占有CSS 変数の契約 をコメント付きで増やす——この順番を守ると、案件間の CSS が散らかりにくい。