よしあきは、Vite + EJS + Sass で静的サイトを組み立てる制作テンプレートを日々使っている。その中で、ホバー演出やボタン装飾を mixin に切り出して再利用 している。案件ごとに CSS を書き直すより、同じ見え方・同じアクセシビリティ配慮 を短い @include で呼べるほうが、レビューと移植(WordPress テーマへ持っていくときなど)が楽になる。
本記事は、その mixin 群を 用途別のカタログ としてまとめたもの。コードの正本は制作テンプレート側の src/assets/sass/global/mixins/ に置いてあり、各ファイル冒頭にも使い方コメントがある。目で確認できるデモは 静的テンプレのデモ一覧 以下(ホバー系は ボタン・テキスト・カード)。各デモには @include の記述例も載せている。
読み方
例 — 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(:root で 0.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 では、サイト内リンク a に hover-opacity(既定で不透明度 0.7)が当たっている。ボタンやナビ項目に別のホバー mixin を足すと、:hover で opacity: 1 を戻す処理が mixin 側に入っている。これを忘れると、意図した色変化のあとリンク全体が薄く見える。
擬似要素の競合に注意
::before / ::after を mixin 内で使うもの(背景スライド、リップル、アイコンなど)は、同じセレクタで別用途の擬似要素を置かない前提。コメントにも「他用途と競合させない」と書いてある。
一覧(用途別)
シンプルなホバー
| mixin | 概要 | 主な引数 |
|---|---|---|
hover-opacity | 不透明度を変える | $opacity(既定 0.7) |
hover-brightness | filter: brightness() | $brightness(既定 1.1) |
hover-scale | 要素全体を拡大 | $scale, $duration, $easing |
hover-scale-img | 子の img だけ拡大(親 figure に overflow: hidden) | 同上 |
hover-up | translateY で浮かせる | $translateY(既定 -4px) |
hover-shadow | box-shadow と軽い上方向移動 | $translate-y(既定 -2px) |
デモ: ホバーエフェクト(カード)
.p-card__link {
@include hover-up();
}
.p-card__thumb {
@include hover-scale-img();
}
背景・ボタン向け
| mixin | 概要 | 擬似要素 |
|---|---|---|
hover-bg | 背景色・文字色を CSS 変数で切替 | なし |
hover-bg-position-slide | background-position をスライド | なし |
hover-bg-slide-before | 左から右へ背景が scaleX で伸びる | ::before |
hover-ripple-after | 中心から円が広がるリップル風 | ::after |
hover-bg-slide-before は、ホバー完了後に本体の background-color を透明にする $clear-bg-delay も取れる。元の背景色が滲むときの逃げ道だ。
デモ: ホバーエフェクト(ボタン)(背景・スケール・スライド・シャドウ・リップル・アイコン・グラデーション)
テキスト装飾
| mixin | 概要 | マークアップ注意 |
|---|---|---|
hover-text-underline | text-decoration-color を透明→表示 | 特になし |
hover-text-underline-span-before | 直下 span::before で左→右の下線 | テキストを直下 span で包む |
text-clip | background-clip: text でグラデ文字 | 背景画像引数可 |
ヘッダーナビなどでは hover-text-underline-span-before を、デモページ(ホバーエフェクト(テキスト))では両方の下線 mixin を並べて比較できるようにしている。
アイコン付きボタン
| mixin | 概要 |
|---|---|
button-icon-after | mask で 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 が散らかりにくい。