アニメーションアイコン
ホバーやクリックで状態が切り替わるSVGアイコン集(メニュー/いいね/完了/通知)。マイクロインタラクションに。
ライブデモ
使用例(お題: 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で提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。