GODRICH
#cssアニメーション#ボタン#ローディング#トランジション#スクロール#ダッシュボード#実用ui#デザイントレンド

【一行でコピペOK】CSS ダークモード 切り替え アニメーション9選

css ダークモード 切り替え は、ページの配色を light と dark で入れ替えるトグルの動きを CSS だけで作る手法です。この記事では9種類を選び、アイコンの回転から light-dark() 関数まで、実装ごとにコードをそろえました。

自動再生 · タイルを押すとその項目へ · すべて1つのzip

グリッドの9つは iframe の中で、2秒ごとにライト→ダーク→ライトへ自動で切り替わります。クリックはできず、動きを眺めるだけの一覧です。並び順は、アイコンやノブ単体が変わるもの(01・02・05・06・07)から、パネル全体の配色が一度に切り替わるもの(03・04・08)、最後に3段階の状態を持つトグル(09)という、実装の複雑さが増える順です。項目ごとに要点となるコードだけを載せているので、完全なファイルは zip の中にあります。

01太陽↔月アイコン切替

どんな動きか

トグルを押すと太陽アイコンが回転しながら月アイコンへクロスフェードします。

太陽↔月アイコン切替
新しいタブで開く

01 · 太陽↔月アイコン切替 — 計測 累積面積 26.961% · 動いたフレーム 11/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

400ms회전 180°크로스페이드

css アニメーション ボタンの使いどころ

実装では300ms・spring系イージングを使うと、勢いよく回りながらも行き過ぎない動きになります。ヘッダーのダークモードボタンや設定メニューに向いています。

.toggle__sun {
  background: $stage-yellow;
  animation: sun $dur-loop $easing infinite;
}
.toggle__moon {
  background: $subject-cream;
  animation: moon $dur-loop $easing infinite;
}

@keyframes sun {
  0%, 15%   { opacity: 1; transform: rotate(0deg); }
  40%, 60%  { opacity: 0; transform: rotate(90deg); }
  85%, 100% { opacity: 1; transform: rotate(0deg); }
}
@keyframes moon {
  0%, 15%   { opacity: 0; transform: rotate(-90deg); }
  40%, 60%  { opacity: 1; transform: rotate(0deg); }
  85%, 100% { opacity: 0; transform: rotate(-90deg); }
}

02ノブスライド

何が起きるか

スイッチのトラック上でノブが右へ滑り、背景色も同時に切り替わります。

ノブスライド
新しいタブで開く

02 · ノブスライド — 計測 累積面積 26.944% · 動いたフレーム 14/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

28px 트랙느리게 600ms그림자 추가

向いている場面

600ms・ease-outで、28px幅のノブがゆっくり移動するぶん状態の変化が目で追いやすくなります。設定画面のトグルやナビ右側のボタンに向いています。

.track__bg--dark {
  background: $subject-ink;
  opacity: 0;
  animation: face-dark $dur-loop $easing infinite;
}
.knob {
  transform: translateX(0);
  animation: knob-slide $dur-loop $easing infinite;
}

@keyframes knob-slide {
  0%, 15%   { transform: translateX(0); }
  40%, 60%  { transform: translateX(20px); }
  85%, 100% { transform: translateX(0); }
}

03クリック地点から円形に切替(View Transitions)

動きの内容

トグル位置(パネル右上)を起点に円が広がり、パネル全体がダークモードに覆われます。

クリック地点から円形に切替(View Transitions)
新しいタブで開く

03 · クリック地点から円形に切替(View Transitions) — 計測 累積面積 26.663% · 動いたフレーム 19/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

중앙에서1.2배속사각 확산

使う場面

実際に組み込む際は View Transitions API のクリックイベントに合わせて再生すると、本物のクリック地点から画面全体が切り替わる演出になります。LPヒーローのトグルやアプリの最初の画面に向いています。

.wipe {
  top: -60px; right: -60px;
  width: 320px; height: 320px;
  border-radius: 50%;
  transform-origin: 78% 22%;
  transform: scale(0);
  animation: wipe-scale $dur-loop $easing infinite;
}

@keyframes wipe-scale {
  0%, 10%   { transform: scale(0); }
  45%, 55%  { transform: scale(1); }
  90%, 100% { transform: scale(0); }
}

04ライトとダークを一気に反転

どんな動きか

2つのスウォッチが交互にフェードし、変数をひとつ切り替えるだけで配色全体が反転する様子を示します。

ライトとダークを一気に反転
新しいタブで開く

04 · ライトとダークを一気に反転 — 計測 累積面積 26.944% · 動いたフレーム 14/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

변수 2개전환 300ms테두리 포함

使いどころ

実際の実装では color-scheme を light と dark で切り替え、background や color を light-dark() 関数一発で書けます。対応状況は MDN の light-dark() 対応表 にあり、Chrome 123以降・Safari 17.5以降・Firefox 120以降で使えます。

.chip__swatch--light {
  background: $color;
  animation: face-light $dur-loop $easing infinite;
}
.chip__swatch--dark {
  background: $stage-yellow;
  opacity: 0;
  animation: face-dark $dur-loop $easing infinite;
}

@keyframes face-light { 0%, 15% { opacity: 1; } 40%, 60% { opacity: 0; } 85%, 100% { opacity: 1; } }
@keyframes face-dark  { 0%, 15% { opacity: 0; } 40%, 60% { opacity: 1; } 85%, 100% { opacity: 0; } }

05日食マスク切替

何が起きるか

月形のマスクが横から滑ってきて太陽アイコンを覆い、ダークモードへ切り替わります。

日食マスク切替
新しいタブで開く

05 · 日食マスク切替 — 計測 累積面積 26.797% · 動いたフレーム 11/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

500ms그림자 진하게역방향 재생

向いている場面

600ms・spring系イージングで同じキーフレームを往復させるだけなので、戻すときに新しいアニメーションを足す必要がありません。ブランドを見せたいトグルやプロダクト紹介ページに向いています。

.mask {
  bottom: clamp(6px, 4vw, 12px);
  left: clamp(0px, 2vw, 4px);
  width: 26px; height: 26px;
  border-radius: 50%;
  background: $stage-ink;
  transform: translateX(-60px);
  animation: mask-slide $dur-loop $easing infinite;
}

@keyframes mask-slide {
  0%, 15%   { transform: translateX(-60px); }
  40%, 60%  { transform: translateX(4px); }
  85%, 100% { transform: translateX(-60px); }
}

06カード反転切替

動きの内容

トグルカードがY軸で反転し、表の太陽から裏の月に変わります。

カード反転切替
新しいタブで開く

06 · カード反転切替 — 計測 累積面積 22.674% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

600msperspective 800px그림자 따라 이동

使う場面

perspective: 900pxとbackface-visibility: hiddenを両方指定しないと、裏面が透けて一瞬だけ両面が重なって見えます。カード型の設定スイッチやプロフィールメニューに向いています。

.card__inner {
  transform-style: preserve-3d;
  animation: flip $dur-loop $easing infinite;
}
.card__face {
  backface-visibility: hidden;
}
.card__face--back {
  transform: rotateY(180deg);
}

@keyframes flip {
  0%, 15%   { transform: rotateY(0deg); }
  50%       { transform: rotateY(180deg); }
  85%, 100% { transform: rotateY(360deg); }
}

07アイコンモーフィング

どんな動きか

太陽アイコンの光線がrotateとscaleY(0)で折りたたまれ、代わりに月のクレーターが浮かび上がります。

アイコンモーフィング
新しいタブで開く

07 · アイコンモーフィング — 計測 累積面積 0.483% · 動いたフレーム 10/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

SVG path350mseasing 스냅

使いどころ

220ms・pop系イージングで素早く畳まれるので、アイコンボタンやツールバーのトグルでも動きが邪魔になりません。SVGを使わず、divとborder-radiusだけで作っています。

.icon__ray {
  transform-origin: 50% 17px;
  transform: rotate(calc(var(--i) * 60deg)) translateY(-17px) scaleY(1);
  animation: ray-fold $dur-loop $easing infinite;
}

@keyframes ray-fold {
  0%, 15%   { opacity: 1; transform: rotate(calc(var(--i) * 60deg)) translateY(-17px) scaleY(1); }
  40%, 60%  { opacity: 0; transform: rotate(calc(var(--i) * 60deg)) translateY(-17px) scaleY(0); }
  85%, 100% { opacity: 1; transform: rotate(calc(var(--i) * 60deg)) translateY(-17px) scaleY(1); }
}

08全体の色がフェード切替

何が起きるか

背景と文字色が同時にフェードし、ボタンなしでパネル全体の配色が入れ替わります。

全体の色がフェード切替
新しいタブで開く

08 · 全体の色がフェード切替 — 計測 累積面積 26.961% · 動いたフレーム 12/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

800ms필터 blur 2px즉시 전환

向いている場面

600ms・ease-outでopacityだけを動かすので、フィルターやぼかしを足さなくても十分滑らかです。ページ全体のテーマ切替やドキュメントビューアに向いています。

.panel__face--light {
  opacity: 1;
  animation: face-light $dur-loop $easing infinite;
}
.panel__face--dark {
  opacity: 0;
  animation: face-dark $dur-loop $easing infinite;
}

@keyframes face-light { 0%, 15% { opacity: 1; } 40%, 60% { opacity: 0; } 85%, 100% { opacity: 1; } }
@keyframes face-dark  { 0%, 15% { opacity: 0; } 40%, 60% { opacity: 1; } 85%, 100% { opacity: 0; } }

09ライト・ダーク・自動の3段階

動きの内容

3コマのトラック上でインジケーターがライト→ダーク→自動の順に移動します。

ライト・ダーク・自動の3段階
新しいタブで開く

09 · ライト・ダーク・自動の3段階 — 計測 累積面積 1.517% · 動いたフレーム 8/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

3단 트랙350ms인디케이터 그림자

使う場面

300ms・spring系イージングでbox-shadow付きのつまみが弾むように止まるので、選んだ状態がひと目で分かります。詳細設定ページやデスクトップアプリの環境設定に向いています。

.segmented__thumb {
  transform: translateX(0);
  animation: thumb-move $dur-loop $easing infinite;
}

@keyframes thumb-move {
  0%, 22%   { transform: translateX(0); }
  33%, 55%  { transform: translateX(32px); }
  66%, 88%  { transform: translateX(64px); }
  100%      { transform: translateX(0); }
}

どこで崩れるか — 落とし穴

opacity と transform だけの理由

9つとも opacity と transform しか動かさない理由は、background-color や width を直接アニメーションさせるとブラウザがレイアウトや再描画を毎フレーム計算し直すからです。MDN の prefers-color-scheme にあるとおり、OS側のダークモード設定はあくまで初期値の目安で、ここで作るトグルのように後から手動で上書きできる状態と合わせて設計する必要があります。

クラス名の衝突

css ボタン アニメーション シンプルな9つを並べてみて気づいたのが、.maskや.knob、.iconのようなクラス名がどれも短く汎用的なことです。既存のコンポーネントに同じ名前のクラスがあると、意図しないスタイルが重なって片方だけ動かなくなります。zipのファイルではそのままの名前で置いていますが、実際のプロジェクトに移すときはコンポーネント単位で接頭辞を付けるか、CSS Modulesのようなスコープの仕組みに通すことをおすすめします。

デモは無限ループ

もうひとつの罠は、この9ファイルがどれもデモ用の無限ループだけで、クリックでテーマを切り替える JavaScript を持たないことです。修正済みの9ファイルを開くには kjdwbr3q という文字列が必要です。下のカードから zip を取ってください。

アクセシビリティ

動きを減らす設定のとき

9つとも prefers-reduced-motion: reduce で animation: none になり、既定ではライト状態のまま止まります。ただし03の円形展開と06のカード反転は「動きを止める」と「状態を伝える」が別物なので、止めた後もトグルの見た目でどちらのテーマかが分かるようにしてあります。09の3段階トグルだけは、動きを止めても選択中のコマにインジケーターをそのまま表示します。

@media (prefers-reduced-motion: reduce) {
  .toggle__sun, .toggle__moon, .knob, .wipe,
  .card__inner, .icon__ray, .segmented__thumb { animation: none !important; }
}

ここで扱わなかった css 画面切り替え アニメーションは CSS カテゴリ に一覧があり、サイトの成り立ちは 紹介ページ に書いています。

FAQ

css ボタン コピペで9つとも本番のトグルボタンにそのまま使えますか?

デモのCSSはどれも$dur-loopの無限ループで自動再生する見た目確認用のファイルです。実際にクリックでテーマを切り替えるには、ボタンのクリックイベントで<html>要素にdata-theme属性を付け外しし、その属性の有無で各アニメーションのクラスを切り替えるJavaScriptを別途書く必要があります。zipのreact/フォルダには、そのクラス切り替えまで含めたコンポーネント版が入っています。

CSSだけでライト・ダークを判定できますか?

OS側の設定に合わせるだけならprefers-color-schemeメディアクエリで判定できますが、ここで紹介した9つのようにユーザーがボタンで手動切り替えできるようにするには、選んだ状態をdata属性かclassとして保持する必要があります。04のlight-dark()関数も、あくまでcolor-schemeプロパティで指定した値を参照するだけなので、その値を切り替えるには別途JavaScriptか設定UIが必要です。

スマホでも同じアニメーションが見えますか?

はい、動きます。09の3段階トグルのようにタップ操作の当たり判定が狭いUIは、96px幅のトラックのままだとスマホの指では押しづらいので、実機では最小44px以上のタップ領域を別途確保することをおすすめします。

解凍パスワードを入力してください

パスワードはこの記事の本文の中にあります。読み進めると出てきます。