アニメーションアイコン

ホバーやクリックで状態が切り替わるSVGアイコン集(メニュー/いいね/完了/通知)。マイクロインタラクションに。

#svg#css#js#icon

ライブデモ

使用例(お題: SaaS FlowDesk)

この技法を「SaaS FlowDesk」というテーマのダミーサイトで実際に使った例です。

HTML
<!-- FlowDesk:機能紹介セクション。クリック/ホバーで動くアイコンが主役 -->
<section class="fd-feat">
  <header class="fd-feat__head">
    <span class="fd-feat__eyebrow">FEATURES</span>
    <h2 class="fd-feat__title">チームの動きを、ひと目で。</h2>
    <p class="fd-feat__lead">アイコンをタップして、各機能をお試しください。</p>
  </header>

  <!-- 主役:状態が切り替わるSVGアイコン群 -->
  <div class="fd-icons">
    <!-- タスク同期:チェック描画 -->
    <button class="fd-icard" data-icon="check" type="button" aria-pressed="false" aria-label="タスク完了">
      <span class="fd-icard__ring">
        <svg viewBox="0 0 44 44" class="ic ic-check">
          <circle class="ring" cx="22" cy="22" r="13" />
          <path class="tick" d="M15 22 l5 5 l9 -11" />
        </svg>
      </span>
      <b>タスク管理</b>
      <span>完了で自動同期</span>
    </button>

    <!-- お気に入り:ハート塗り -->
    <button class="fd-icard" data-icon="heart" type="button" aria-pressed="false" aria-label="お気に入り">
      <span class="fd-icard__ring">
        <svg viewBox="0 0 44 44" class="ic ic-heart">
          <path d="M22 33 C8 24 8 14 15 12 C19 11 22 15 22 15 C22 15 25 11 29 12 C36 14 36 24 22 33 Z" />
        </svg>
      </span>
      <b>お気に入り</b>
      <span>よく使う画面を固定</span>
    </button>

    <!-- メニュー:ハンバーガー→X -->
    <button class="fd-icard" data-icon="menu" type="button" aria-pressed="false" aria-label="メニュー切替">
      <span class="fd-icard__ring">
        <svg viewBox="0 0 44 44" class="ic ic-menu">
          <line class="top" x1="12" y1="16" x2="32" y2="16" />
          <line class="mid" x1="12" y1="22" x2="32" y2="22" />
          <line class="bot" x1="12" y1="28" x2="32" y2="28" />
        </svg>
      </span>
      <b>クイック操作</b>
      <span>どこでも呼び出し</span>
    </button>

    <!-- 通知:ベルが揺れる(ホバー) -->
    <button class="fd-icard" data-icon="bell" type="button" aria-label="通知">
      <span class="fd-icard__ring">
        <svg viewBox="0 0 44 44" class="ic ic-bell">
          <path class="body" d="M22 11 C16 11 14 15 14 20 C14 27 11 28 11 30 L33 30 C33 28 30 27 30 20 C30 15 28 11 22 11 Z" />
          <path class="clap" d="M19 33 a3 3 0 0 0 6 0" />
        </svg>
      </span>
      <b>リアル通知</b>
      <span>変更を即お届け</span>
    </button>
  </div>
</section>
CSS
/* FlowDesk:機能紹介セクション(アニメーションアイコン) */
:root {
  --navy: #0f1b34;
  --blue: #4f7cff;
  --line: #e7ecf6;
  --text: #56607d;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  height: 400px;
  display: grid;
  place-items: center;
  background: linear-gradient(180deg, #fff 0%, #eef2fb 100%);
  font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
  color: var(--text);
  overflow: hidden;
}

.fd-feat { width: min(620px, 94vw); text-align: center; padding: 8px; }
.fd-feat__eyebrow {
  font-size: 11px;
  letter-spacing: 0.26em;
  font-weight: 700;
  color: var(--blue);
}
.fd-feat__title { margin: 6px 0 6px; font-size: 22px; font-weight: 800; color: var(--navy); }
.fd-feat__lead { margin: 0 0 20px; font-size: 13px; }

/* アイコンカード並び */
.fd-icons {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
}
.fd-icard {
  display: grid;
  justify-items: center;
  gap: 4px;
  padding: 16px 8px 14px;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 16px;
  cursor: pointer;
  color: var(--text);
  transition: border-color 0.2s, transform 0.12s, box-shadow 0.2s;
}
.fd-icard:hover { border-color: var(--blue); box-shadow: 0 12px 26px -14px rgba(79, 124, 255, 0.6); }
.fd-icard:active { transform: scale(0.97); }
.fd-icard b { font-size: 13px; color: var(--navy); }
.fd-icard span { font-size: 10.5px; color: #8b95b0; }

/* アイコン土台 */
.fd-icard__ring {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  margin-bottom: 6px;
  border-radius: 14px;
  background: #eef2fb;
}
.ic { width: 36px; height: 36px; }
.ic line, .ic path, .ic circle {
  fill: none;
  stroke: var(--navy);
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- menu: 線を回転してXに --- */
.ic-menu line { transition: transform 0.35s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.2s; transform-origin: center; }
.fd-icard[data-icon="menu"][aria-pressed="true"] .top { transform: translateY(6px) rotate(45deg); }
.fd-icard[data-icon="menu"][aria-pressed="true"] .mid { opacity: 0; }
.fd-icard[data-icon="menu"][aria-pressed="true"] .bot { transform: translateY(-6px) rotate(-45deg); }

/* --- heart: 押すと塗り+ポップ --- */
.ic-heart path {
  stroke: var(--blue);
  transition: fill 0.25s, transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
  transform-origin: center;
  transform-box: fill-box;
}
.fd-icard[data-icon="heart"]:hover path { transform: scale(1.08); }
.fd-icard[data-icon="heart"][aria-pressed="true"] path { fill: var(--blue); animation: fdBeat 0.5s ease; }
@keyframes fdBeat { 30% { transform: scale(1.25); } 60% { transform: scale(0.92); } }

/* --- check: リング+チェックを描画 --- */
.ic-check .ring { stroke: #2bb673; stroke-dasharray: 82; stroke-dashoffset: 82; }
.ic-check .tick { stroke: #2bb673; stroke-dasharray: 24; stroke-dashoffset: 24; }
.fd-icard[data-icon="check"][aria-pressed="true"] .ring { animation: fdDraw 0.5s ease forwards; }
.fd-icard[data-icon="check"][aria-pressed="true"] .tick { animation: fdDraw 0.35s ease 0.35s forwards; }
@keyframes fdDraw { to { stroke-dashoffset: 0; } }

/* --- bell: ホバーで揺れる --- */
.ic-bell { transform-origin: 22px 11px; }
.fd-icard[data-icon="bell"]:hover .ic-bell { animation: fdRing 1s ease; }
@keyframes fdRing {
  0%, 100% { transform: rotate(0); }
  20% { transform: rotate(14deg); }
  40% { transform: rotate(-11deg); }
  60% { transform: rotate(7deg); }
  80% { transform: rotate(-4deg); }
}

@media (prefers-reduced-motion: reduce) {
  .ic *, .ic { animation: none !important; transition: none !important; }
  .ic-check .ring, .ic-check .tick { stroke-dashoffset: 0; }
  .fd-icard { transition: none; }
}
JavaScript
// アイコンのトグル状態を aria-pressed で管理(見た目はCSSが切替)
const cards = document.querySelectorAll(".fd-icard[data-icon]");

cards.forEach((card) => {
  const type = card.dataset.icon;
  if (type === "bell") return; // bellはホバー演出のみ

  card.addEventListener("click", () => {
    const pressed = card.getAttribute("aria-pressed") === "true";
    card.setAttribute("aria-pressed", String(!pressed));

    // check は解除時にアニメをリセットして再生できるように
    if (type === "check" && pressed) {
      const ring = card.querySelector(".ring");
      const tick = card.querySelector(".tick");
      [ring, tick].forEach((el) => {
        if (!el) return;
        el.style.animation = "none";
        void el.offsetWidth; // リフロー
        el.style.animation = "";
      });
    }
  });
});

コード

HTML
<!-- アニメーションアイコン: ホバー/クリックで状態が切り替わるSVGアイコン群 -->
<div class="icon-stage">
  <!-- ハンバーガー → クローズ (クリックでトグル) -->
  <button class="icon-card" data-icon="menu" type="button" aria-pressed="false" aria-label="メニュー切替">
    <svg viewBox="0 0 44 44" class="ic ic-menu">
      <line class="top" x1="12" y1="16" x2="32" y2="16" />
      <line class="mid" x1="12" y1="22" x2="32" y2="22" />
      <line class="bot" x1="12" y1="28" x2="32" y2="28" />
    </svg>
    <span class="cap">menu</span>
  </button>

  <!-- ハート (クリックでいいね) -->
  <button class="icon-card" data-icon="heart" type="button" aria-pressed="false" aria-label="いいね">
    <svg viewBox="0 0 44 44" class="ic ic-heart">
      <path d="M22 33 C8 24 8 14 15 12 C19 11 22 15 22 15 C22 15 25 11 29 12 C36 14 36 24 22 33 Z" />
    </svg>
    <span class="cap">like</span>
  </button>

  <!-- チェック (クリックで完了マーク) -->
  <button class="icon-card" data-icon="check" type="button" aria-pressed="false" aria-label="完了">
    <svg viewBox="0 0 44 44" class="ic ic-check">
      <circle class="ring" cx="22" cy="22" r="13" />
      <path class="tick" d="M15 22 l5 5 l9 -11" />
    </svg>
    <span class="cap">done</span>
  </button>

  <!-- ベル (ホバーで揺れる) -->
  <button class="icon-card" data-icon="bell" type="button" aria-label="通知">
    <svg viewBox="0 0 44 44" class="ic ic-bell">
      <path class="body" d="M22 11 C16 11 14 15 14 20 C14 27 11 28 11 30 L33 30 C33 28 30 27 30 20 C30 15 28 11 22 11 Z" />
      <path class="clap" d="M19 33 a3 3 0 0 0 6 0" />
    </svg>
    <span class="cap">bell</span>
  </button>
</div>
CSS
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", system-ui, sans-serif;
  background: radial-gradient(120% 120% at 50% 0%, #111827 0%, #0b1120 60%, #060912 100%);
}
.icon-stage {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  justify-content: center;
}
.icon-card {
  display: grid;
  justify-items: center;
  gap: 8px;
  width: 88px;
  padding: 16px 8px 12px;
  background: rgba(255, 255, 255, .04);
  border: 1px solid rgba(255, 255, 255, .08);
  border-radius: 16px;
  cursor: pointer;
  color: #94a3b8;
  transition: background .25s, border-color .25s, transform .12s;
}
.icon-card:hover { background: rgba(255, 255, 255, .07); border-color: rgba(99, 102, 241, .5); }
.icon-card:active { transform: scale(.96); }
.cap { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; }

.ic { width: 44px; height: 44px; }
.ic line, .ic path, .ic circle {
  fill: none;
  stroke: #e2e8f0;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- menu: 線を回転させてXに --- */
.ic-menu line { transition: transform .35s cubic-bezier(.65, 0, .35, 1), opacity .2s; transform-origin: center; }
.icon-card[data-icon="menu"][aria-pressed="true"] .top { transform: translateY(6px) rotate(45deg); }
.icon-card[data-icon="menu"][aria-pressed="true"] .mid { opacity: 0; }
.icon-card[data-icon="menu"][aria-pressed="true"] .bot { transform: translateY(-6px) rotate(-45deg); }

/* --- heart: 押すと塗り+ポップ --- */
.ic-heart path {
  stroke: #fb7185;
  transition: fill .25s, transform .3s cubic-bezier(.34, 1.56, .64, 1);
  transform-origin: center;
  transform-box: fill-box;
}
.icon-card[data-icon="heart"]:hover path { transform: scale(1.08); }
.icon-card[data-icon="heart"][aria-pressed="true"] path {
  fill: #fb7185;
  animation: beat .5s ease;
}
@keyframes beat { 30% { transform: scale(1.25); } 60% { transform: scale(.92); } }

/* --- check: リング+チェックを描画 --- */
.ic-check .ring { stroke: #34d399; stroke-dasharray: 82; stroke-dashoffset: 82; }
.ic-check .tick { stroke: #34d399; stroke-dasharray: 24; stroke-dashoffset: 24; }
.icon-card[data-icon="check"][aria-pressed="true"] .ring { animation: draw .5s ease forwards; }
.icon-card[data-icon="check"][aria-pressed="true"] .tick { animation: draw .35s ease .35s forwards; }
@keyframes draw { to { stroke-dashoffset: 0; } }

/* --- bell: ホバーで揺れる --- */
.ic-bell { transform-origin: 22px 11px; }
.icon-card[data-icon="bell"]:hover .ic-bell { animation: ring 1s ease; }
@keyframes ring {
  0%,100% { transform: rotate(0); }
  20% { transform: rotate(14deg); }
  40% { transform: rotate(-11deg); }
  60% { transform: rotate(7deg); }
  80% { transform: rotate(-4deg); }
}

@media (prefers-reduced-motion: reduce) {
  .ic *, .ic { animation: none !important; transition: none !important; }
  .ic-check .ring, .ic-check .tick { stroke-dashoffset: 0; }
}
JavaScript
// アイコンのトグル状態を aria-pressed で管理(CSSが見た目を切替)
const cards = document.querySelectorAll(".icon-card[data-icon]");

cards.forEach((card) => {
  const type = card.dataset.icon;
  // bell はホバー演出のみなのでクリックトグル不要
  if (type === "bell") return;

  card.addEventListener("click", () => {
    const pressed = card.getAttribute("aria-pressed") === "true";
    card.setAttribute("aria-pressed", String(!pressed));

    // check は一度だけ描いたら再描画できるようリセット可能に
    if (type === "check" && pressed) {
      // 解除時にアニメをリセットして再生できるようにする
      const ring = card.querySelector(".ring");
      const tick = card.querySelector(".tick");
      [ring, tick].forEach((el) => {
        if (!el) return;
        el.style.animation = "none";
        void el.offsetWidth; // リフロー
        el.style.animation = "";
      });
    }
  });
});

🤖 AIエージェント用プロンプト

このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「アニメーションアイコン」の効果を追加してください。

# 追加してほしい効果
アニメーションアイコン(SVG エフェクト)
ホバーやクリックで状態が切り替わるSVGアイコン集(メニュー/いいね/完了/通知)。マイクロインタラクションに。

# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- アニメーションアイコン: ホバー/クリックで状態が切り替わるSVGアイコン群 -->
<div class="icon-stage">
  <!-- ハンバーガー → クローズ (クリックでトグル) -->
  <button class="icon-card" data-icon="menu" type="button" aria-pressed="false" aria-label="メニュー切替">
    <svg viewBox="0 0 44 44" class="ic ic-menu">
      <line class="top" x1="12" y1="16" x2="32" y2="16" />
      <line class="mid" x1="12" y1="22" x2="32" y2="22" />
      <line class="bot" x1="12" y1="28" x2="32" y2="28" />
    </svg>
    <span class="cap">menu</span>
  </button>

  <!-- ハート (クリックでいいね) -->
  <button class="icon-card" data-icon="heart" type="button" aria-pressed="false" aria-label="いいね">
    <svg viewBox="0 0 44 44" class="ic ic-heart">
      <path d="M22 33 C8 24 8 14 15 12 C19 11 22 15 22 15 C22 15 25 11 29 12 C36 14 36 24 22 33 Z" />
    </svg>
    <span class="cap">like</span>
  </button>

  <!-- チェック (クリックで完了マーク) -->
  <button class="icon-card" data-icon="check" type="button" aria-pressed="false" aria-label="完了">
    <svg viewBox="0 0 44 44" class="ic ic-check">
      <circle class="ring" cx="22" cy="22" r="13" />
      <path class="tick" d="M15 22 l5 5 l9 -11" />
    </svg>
    <span class="cap">done</span>
  </button>

  <!-- ベル (ホバーで揺れる) -->
  <button class="icon-card" data-icon="bell" type="button" aria-label="通知">
    <svg viewBox="0 0 44 44" class="ic ic-bell">
      <path class="body" d="M22 11 C16 11 14 15 14 20 C14 27 11 28 11 30 L33 30 C33 28 30 27 30 20 C30 15 28 11 22 11 Z" />
      <path class="clap" d="M19 33 a3 3 0 0 0 6 0" />
    </svg>
    <span class="cap">bell</span>
  </button>
</div>

【CSS】
* { box-sizing: border-box; }
body {
  margin: 0;
  min-height: 100vh;
  display: grid;
  place-items: center;
  font-family: "Segoe UI", system-ui, sans-serif;
  background: radial-gradient(120% 120% at 50% 0%, #111827 0%, #0b1120 60%, #060912 100%);
}
.icon-stage {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  justify-content: center;
}
.icon-card {
  display: grid;
  justify-items: center;
  gap: 8px;
  width: 88px;
  padding: 16px 8px 12px;
  background: rgba(255, 255, 255, .04);
  border: 1px solid rgba(255, 255, 255, .08);
  border-radius: 16px;
  cursor: pointer;
  color: #94a3b8;
  transition: background .25s, border-color .25s, transform .12s;
}
.icon-card:hover { background: rgba(255, 255, 255, .07); border-color: rgba(99, 102, 241, .5); }
.icon-card:active { transform: scale(.96); }
.cap { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; }

.ic { width: 44px; height: 44px; }
.ic line, .ic path, .ic circle {
  fill: none;
  stroke: #e2e8f0;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- menu: 線を回転させてXに --- */
.ic-menu line { transition: transform .35s cubic-bezier(.65, 0, .35, 1), opacity .2s; transform-origin: center; }
.icon-card[data-icon="menu"][aria-pressed="true"] .top { transform: translateY(6px) rotate(45deg); }
.icon-card[data-icon="menu"][aria-pressed="true"] .mid { opacity: 0; }
.icon-card[data-icon="menu"][aria-pressed="true"] .bot { transform: translateY(-6px) rotate(-45deg); }

/* --- heart: 押すと塗り+ポップ --- */
.ic-heart path {
  stroke: #fb7185;
  transition: fill .25s, transform .3s cubic-bezier(.34, 1.56, .64, 1);
  transform-origin: center;
  transform-box: fill-box;
}
.icon-card[data-icon="heart"]:hover path { transform: scale(1.08); }
.icon-card[data-icon="heart"][aria-pressed="true"] path {
  fill: #fb7185;
  animation: beat .5s ease;
}
@keyframes beat { 30% { transform: scale(1.25); } 60% { transform: scale(.92); } }

/* --- check: リング+チェックを描画 --- */
.ic-check .ring { stroke: #34d399; stroke-dasharray: 82; stroke-dashoffset: 82; }
.ic-check .tick { stroke: #34d399; stroke-dasharray: 24; stroke-dashoffset: 24; }
.icon-card[data-icon="check"][aria-pressed="true"] .ring { animation: draw .5s ease forwards; }
.icon-card[data-icon="check"][aria-pressed="true"] .tick { animation: draw .35s ease .35s forwards; }
@keyframes draw { to { stroke-dashoffset: 0; } }

/* --- bell: ホバーで揺れる --- */
.ic-bell { transform-origin: 22px 11px; }
.icon-card[data-icon="bell"]:hover .ic-bell { animation: ring 1s ease; }
@keyframes ring {
  0%,100% { transform: rotate(0); }
  20% { transform: rotate(14deg); }
  40% { transform: rotate(-11deg); }
  60% { transform: rotate(7deg); }
  80% { transform: rotate(-4deg); }
}

@media (prefers-reduced-motion: reduce) {
  .ic *, .ic { animation: none !important; transition: none !important; }
  .ic-check .ring, .ic-check .tick { stroke-dashoffset: 0; }
}

【JavaScript】
// アイコンのトグル状態を aria-pressed で管理(CSSが見た目を切替)
const cards = document.querySelectorAll(".icon-card[data-icon]");

cards.forEach((card) => {
  const type = card.dataset.icon;
  // bell はホバー演出のみなのでクリックトグル不要
  if (type === "bell") return;

  card.addEventListener("click", () => {
    const pressed = card.getAttribute("aria-pressed") === "true";
    card.setAttribute("aria-pressed", String(!pressed));

    // check は一度だけ描いたら再描画できるようリセット可能に
    if (type === "check" && pressed) {
      // 解除時にアニメをリセットして再生できるようにする
      const ring = card.querySelector(".ring");
      const tick = card.querySelector(".tick");
      [ring, tick].forEach((el) => {
        if (!el) return;
        el.style.animation = "none";
        void el.offsetWidth; // リフロー
        el.style.animation = "";
      });
    }
  });
});

# 外部ライブラリ
なし(追加ライブラリ不要)

# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。