HOME / BLOG
BLOG

SVGアイコンをCSSだけでアニメーションさせる方法

SVGアイコンは、画像ファイルとして表示するだけでなく、CSSから色、回転、拡大縮小、線の描画などをアニメーションできます。ローディング、メニューボタン、矢印、チェックマークなど、UIの細かな反応をJavaScriptなしで作れるのが魅力です。

この記事では、インラインSVGをCSSで操作する基本から、ホバー、線描画、パーツごとの遅延、モーション軽減設定まで、コピペして応用できる実装を紹介します。

CSSでSVGを動かすための前提

SVG内部のpathcircleをCSSで選択したい場合は、SVGをHTMLへインラインで記述します。<img src="icon.svg">として読み込んだ外部SVGの内部要素は、通常のページCSSから直接選択できません。

<button class="icon-button" type="button" aria-label="更新">
  <svg class="icon icon-refresh" viewBox="0 0 24 24" aria-hidden="true">
    <path d="M20 11a8 8 0 1 0-2.34 5.66" />
    <path d="M20 4v7h-7" />
  </svg>
</button>
.icon {
  width: 24px;
  height: 24px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

currentColorを使うと、アイコンは親要素のcolorを引き継ぎます。通常、ホバー、無効状態などの色管理が簡単になります。

実装例1:ホバーで矢印を動かす

<a class="more-link" href="#">
  詳しく見る
  <svg viewBox="0 0 24 24" aria-hidden="true">
    <path d="M5 12h14M13 6l6 6-6 6" />
  </svg>
</a>
.more-link svg {
  width: 20px;
  transition: transform .2s ease;
}

.more-link:hover svg,
.more-link:focus-visible svg {
  transform: translateX(5px);
}

アイコン単体ではなく、リンク全体のホバーとキーボードフォーカスをトリガーにします。

実装例2:更新アイコンを回転させる

.icon-refresh {
  transform-box: fill-box;
  transform-origin: center;
}

.icon-button:hover .icon-refresh,
.icon-button:focus-visible .icon-refresh {
  animation: icon-spin .7s ease-in-out;
}

@keyframes icon-spin {
  to {
    transform: rotate(360deg);
  }
}

SVGの変形基準を安定させるため、transform-box: fill-boxtransform-origin: centerをセットで指定します。

実装例3:ハートを鼓動させる

.favorite-button svg {
  transform-box: fill-box;
  transform-origin: center;
}

.favorite-button:hover svg,
.favorite-button:focus-visible svg {
  animation: heart-beat .45s ease-in-out;
}

@keyframes heart-beat {
  0%, 100% { transform: scale(1); }
  45%      { transform: scale(1.22); }
  70%      { transform: scale(.96); }
}

拡大率を大きくしすぎると周囲のUIへ接触します。アイコンボタンの領域は固定し、SVGだけを変形させます。

実装例4:チェックマークを線描画する

stroke-dasharrayで線を破線化し、stroke-dashoffsetを線の長さから0へ変化させると、線が描かれるように見せられます。

<svg class="check-icon" viewBox="0 0 48 48" aria-hidden="true">
  <circle cx="24" cy="24" r="20" />
  <path class="check-line" pathLength="1" d="M14 24l7 7 14-16" />
</svg>
.check-line {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.is-complete .check-line {
  animation: draw-check .5s ease forwards;
}

@keyframes draw-check {
  to {
    stroke-dashoffset: 0;
  }
}

pathLength="1"を指定すると、線の実寸を計測せず、1を基準にdash値を扱えます。クラスの付与はサーバー出力、チェックボックス状態、既存UIの状態クラスなどに合わせてください。

実装例5:円形ローダーを作る

<svg class="loader" viewBox="0 0 50 50" role="img" aria-label="読み込み中">
  <circle class="loader-track" cx="25" cy="25" r="20" />
  <circle class="loader-progress" cx="25" cy="25" r="20" />
</svg>
.loader {
  width: 40px;
  animation: loader-rotate 1s linear infinite;
}

.loader circle {
  fill: none;
  stroke-width: 5;
}

.loader-track {
  stroke: #e6e6e6;
}

.loader-progress {
  stroke: #ff2f63;
  stroke-linecap: round;
  stroke-dasharray: 90 150;
  stroke-dashoffset: 0;
}

@keyframes loader-rotate {
  to { transform: rotate(360deg); }
}

実装例6:メニューアイコンを閉じるアイコンへ変形する

<button class="menu-button" type="button" aria-expanded="false">
  <svg viewBox="0 0 24 24" aria-hidden="true">
    <path class="line line-top" d="M4 7h16" />
    <path class="line line-middle" d="M4 12h16" />
    <path class="line line-bottom" d="M4 17h16" />
  </svg>
  <span class="screen-reader-text">メニュー</span>
</button>
.menu-button .line {
  transform-box: fill-box;
  transform-origin: center;
  transition: transform .25s ease, opacity .2s ease;
}

.menu-button[aria-expanded="true"] .line-top {
  transform: translateY(5px) rotate(45deg);
}

.menu-button[aria-expanded="true"] .line-middle {
  opacity: 0;
  transform: scaleX(0);
}

.menu-button[aria-expanded="true"] .line-bottom {
  transform: translateY(-5px) rotate(-45deg);
}

開閉状態はaria-expandedと同期させます。アニメーション自体はCSSだけですが、実際のナビ開閉にはボタン状態を切り替える処理が必要です。

実装例7:ベルを揺らす

.notification-button svg {
  transform-box: fill-box;
  transform-origin: 50% 10%;
}

.notification-button:hover svg,
.notification-button:focus-visible svg {
  animation: bell-ring .6s ease-in-out;
}

@keyframes bell-ring {
  0%, 100% { transform: rotate(0); }
  25%      { transform: rotate(12deg); }
  50%      { transform: rotate(-10deg); }
  75%      { transform: rotate(6deg); }
}

実装例8:複数パーツを順番に表示する

.spark-icon path {
  opacity: 0;
  transform: scale(.6);
  transform-box: fill-box;
  transform-origin: center;
}

.spark-button:hover .spark-icon path,
.spark-button:focus-visible .spark-icon path {
  animation: spark-in .35s ease forwards;
}

.spark-icon path:nth-child(2) { animation-delay: .08s; }
.spark-icon path:nth-child(3) { animation-delay: .16s; }

@keyframes spark-in {
  to {
    opacity: 1;
    transform: scale(1);
  }
}

パーツ数が多いとCSSが複雑になります。2〜4個程度の短い演出に留めると管理しやすく、UIも落ち着きます。

クリック中・選択状態をCSSで表現する

ホバーだけでなく、:active:checkedaria-pressed="true"などの状態を利用できます。

.like-button[aria-pressed="true"] svg {
  fill: currentColor;
  animation: heart-beat .45s ease-in-out;
}

.icon-toggle:checked + label svg {
  transform: rotate(180deg);
}

prefers-reduced-motionに対応する

OSで動きを減らす設定を有効にしている利用者へ、不要な回転や移動を強制しないようにします。

@media (prefers-reduced-motion: reduce) {
  .icon,
  .icon *,
  .loader {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

状態変化そのものは、色、塗り、下線、テキストなど動き以外でも分かるようにしてください。

よくある失敗

  • imgで読み込んだSVG内部を選択しようとする:内部パーツを動かすならインラインSVGにします。
  • transform-originを指定しない:回転や拡大の中心が意図しない位置になります。
  • hoverだけに依存する:キーボード操作の:focus-visibleや状態属性も用意します。
  • 無限アニメーションを多用する:視線を奪い、消費電力や読みやすさにも影響します。
  • SVGへ固定色を直接書く:currentColorを利用すると状態別の色変更が容易です。
  • 意味のあるアイコンをaria-hiddenにする:テキストがない場合はボタンへ適切なラベルを付けます。
  • モーション軽減設定を無視する:prefers-reduced-motionで演出を抑えます。

パフォーマンスを保つコツ

  • transformopacityを中心に使う
  • 一度に動かすパーツを少なくする
  • 常時ループより、操作時の短いフィードバックを優先する
  • SVGの不要なパスやメタデータを削減する
  • アイコンの表示領域を固定してレイアウトを動かさない

まとめ

SVGアイコンはインラインで配置すると、CSSだけで各パーツを細かく制御できます。単純な移動や回転はtransform、線描画はstroke-dasharraystroke-dashoffset、状態管理はホバー、フォーカス、属性セレクターを使うのが基本です。

アニメーションは目立たせるためではなく、押せること、処理中であること、状態が変わったことを伝えるために使うと効果的です。短く、意味があり、動きを減らす設定にも対応した演出を心がけましょう。

参考資料