いいねハートバースト

クリックでハートが弾み、放射状に色とりどりの粒子が飛び散る「いいね」ボタン。SNSやレビューのリアクションUIに使えます。

#js#css#animation#svg

ライブデモ

使用例(お題: アイドルグループ Sakura)

この技法を「アイドルグループ Sakura」というテーマのダミーサイトで実際に使った例です。

HTML
<!-- Sakura: 楽曲MVページ。いいねハートバーストを主役に -->
<div class="sk">
  <div class="mv">
    <img class="mv__thumb" src="https://picsum.photos/480/270?random=53" alt="MVサムネイル">
    <span class="mv__play" aria-hidden="true">▶</span>
    <span class="mv__badge">NEW MV</span>
  </div>

  <div class="mv__bar">
    <div class="mv__meta">
      <h1 class="mv__title">春風セレナーデ - Music Video</h1>
      <p class="mv__by">Sakura ・ 公開3日 ・ 12万回再生</p>
    </div>

    <button class="like" type="button" aria-pressed="false" aria-label="いいね">
      <svg class="like__icon" viewBox="0 0 24 24" aria-hidden="true">
        <path d="M12 21s-7.5-4.7-9.7-9.3C1 8.4 2.6 5.5 5.6 5.1c1.9-.3 3.5.7 4.4 2.1.9-1.4 2.5-2.4 4.4-2.1 3 .4 4.6 3.3 3.3 6.6C19.5 16.3 12 21 12 21z"/>
      </svg>
      <span class="like__count">2480</span>
    </button>
  </div>
  <p class="sk__hint">ハートを押して「いいね」してみてください</p>
</div>
CSS
/* Sakura アイドル テーマ: 桜ピンク/白/淡グレー */
* { box-sizing: border-box; }
body {
  margin: 0;
  font-family: "Hiragino Kaku Gothic ProN", "Segoe UI", system-ui, sans-serif;
  background:
    radial-gradient(circle at 50% -10%, #ffe3ee 0%, transparent 55%),
    #fff7fb;
  color: #6b4a58;
}

.sk {
  height: 400px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 10px;
  padding: 0 28px;
}

/* MVサムネイル */
.mv {
  position: relative;
  border-radius: 16px;
  overflow: hidden;
  box-shadow: 0 18px 40px rgba(232,90,146,.25);
  aspect-ratio: 16 / 7;
}
.mv__thumb {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  filter: saturate(1.05);
}
.mv__play {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 56px;
  height: 56px;
  display: grid;
  place-items: center;
  font-size: 18px;
  color: #e85a92;
  background: rgba(255,255,255,.85);
  border-radius: 50%;
  box-shadow: 0 8px 20px rgba(0,0,0,.2);
  padding-left: 3px;
}
.mv__badge {
  position: absolute;
  left: 12px;
  top: 12px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .08em;
  color: #fff;
  background: #e85a92;
  padding: 4px 11px;
  border-radius: 999px;
}

/* タイトル+いいね行 */
.mv__bar {
  display: flex;
  align-items: center;
  gap: 14px;
}
.mv__meta { flex: 1; min-width: 0; }
.mv__title {
  margin: 0;
  font-size: 16px;
  font-weight: 800;
  color: #4a3340;
}
.mv__by {
  margin: 4px 0 0;
  font-size: 12px;
  color: #b08a9b;
}

/* 主役: いいねハートバースト */
.like {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: none;
  font-size: 14px;
  font-weight: 700;
  color: #b08a9b;
  background: #fff;
  border: 1px solid #ffd6e6;
  border-radius: 999px;
  padding: 9px 18px;
  cursor: pointer;
  transition: color .2s ease, border-color .2s ease, transform .15s ease;
}
.like__icon {
  width: 22px;
  height: 22px;
  fill: #e7b9c9;
  transition: fill .2s ease, transform .25s cubic-bezier(.34,1.56,.64,1);
}
.like.is-active {
  color: #e85a92;
  border-color: #ff9ec2;
}
.like.is-active .like__icon {
  fill: #ff4d8d;
  transform: scale(1.18);
}
.like:active { transform: scale(.96); }

/* 放射状に飛ぶ粒子 */
.spark {
  position: absolute;
  left: 22px;
  top: 50%;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  pointer-events: none;
  animation: spark-out .6s ease-out forwards;
}
@keyframes spark-out {
  0% { transform: translate(-50%, -50%) scale(1); opacity: 1; }
  100% { transform: translate(calc(-50% + var(--dx)), calc(-50% + var(--dy))) scale(0); opacity: 0; }
}

.sk__hint {
  margin: 2px 0 0;
  font-size: 11px;
  color: #c79bb0;
}

@media (prefers-reduced-motion: reduce) {
  .like, .like__icon { transition: none; }
}
JavaScript
// SakuraのMVいいね: トグルで色変化+粒子を放射状に飛ばす
(() => {
  const btn = document.querySelector('.like');
  const count = document.querySelector('.like__count');
  if (!btn || !count) return; // null安全

  const base = parseInt(count.textContent, 10) || 0;
  let liked = false;
  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  const COLORS = ['#ff4d8d', '#ff9ec2', '#ffd166', '#a6d8ff', '#c8a6ff'];

  // 放射状に粒子を生成
  const burst = () => {
    if (reduce) return;
    const N = 10;
    for (let i = 0; i < N; i++) {
      const s = document.createElement('span');
      s.className = 'spark';
      const angle = (Math.PI * 2 * i) / N + Math.random() * 0.4;
      const dist = 24 + Math.random() * 20;
      s.style.setProperty('--dx', `${Math.cos(angle) * dist}px`);
      s.style.setProperty('--dy', `${Math.sin(angle) * dist}px`);
      s.style.background = COLORS[i % COLORS.length];
      s.addEventListener('animationend', () => s.remove());
      btn.appendChild(s);
    }
  };

  btn.addEventListener('click', () => {
    liked = !liked;
    btn.classList.toggle('is-active', liked);
    btn.setAttribute('aria-pressed', String(liked));
    // いいね数を即時反映(整形してカンマ区切り)
    count.textContent = (base + (liked ? 1 : 0)).toLocaleString('ja-JP');
    if (liked) burst(); // 「いいね」した瞬間だけ弾ける
  });
})();

コード

HTML
<!-- いいねハートバースト: クリックで弾けて粒子が飛び散る -->
<div class="stage">
  <button class="like" type="button" aria-pressed="false" aria-label="いいね">
    <svg class="like__heart" viewBox="0 0 24 24" aria-hidden="true">
      <path d="M12 21s-7.5-4.6-10-9.2C.3 8.6 1.8 5 5.2 5c2 0 3.3 1.1 4 2.2C9.9 6.1 11.2 5 13.2 5c3.4 0 4.9 3.6 3.2 6.8C19.5 16.4 12 21 12 21z"/>
    </svg>
    <span class="like__count">128</span>
  </button>
  <p class="hint">クリックして「いいね」</p>
</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: linear-gradient(135deg, #fff1f3 0%, #ffe3ec 100%);
}

.stage {
  display: grid;
  place-items: center;
  gap: 18px;
}

/* いいねボタン */
.like {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 14px 26px;
  border: none;
  border-radius: 999px;
  cursor: pointer;
  background: #fff;
  box-shadow: 0 10px 26px rgba(255, 87, 124, .18);
  transition: transform .12s ease, box-shadow .25s ease;
  -webkit-tap-highlight-color: transparent;
}
.like:active { transform: scale(.95); }

.like__heart {
  width: 30px;
  height: 30px;
  fill: #d7dbe3;
  transition: fill .2s ease, transform .25s cubic-bezier(.68,-0.55,.27,1.55);
}

/* 押下時の弾むスケール(JSが付与) */
.like.is-active .like__heart {
  fill: #ff4d6d;
  animation: heart-pop .45s cubic-bezier(.34,1.56,.64,1);
}
@keyframes heart-pop {
  0%   { transform: scale(.2); }
  45%  { transform: scale(1.35); }
  100% { transform: scale(1); }
}

.like__count {
  font-size: 18px;
  font-weight: 700;
  color: #44424d;
  font-variant-numeric: tabular-nums;
}
.like.is-active .like__count { color: #ff4d6d; }

/* 飛び散る粒子(JSで生成) */
.spark {
  position: absolute;
  top: 50%;
  left: 27px; /* ハート中心付近 */
  width: 8px;
  height: 8px;
  border-radius: 50%;
  pointer-events: none;
  transform: translate(-50%, -50%);
  animation: spark-fly .6s ease-out forwards;
}
@keyframes spark-fly {
  0%   { opacity: 1; transform: translate(-50%, -50%) translate(0, 0) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) translate(var(--dx), var(--dy)) scale(.2); }
}

.hint {
  margin: 0;
  font-size: 13px;
  color: #9a879a;
}

@media (prefers-reduced-motion: reduce) {
  .like.is-active .like__heart { animation: none; }
  .spark { animation-duration: .01ms; }
}
JavaScript
// いいねハートバースト: トグルで色変化+粒子を放射状に飛ばす
(() => {
  const btn = document.querySelector('.like');
  const count = document.querySelector('.like__count');
  if (!btn || !count) return; // null安全

  const base = parseInt(count.textContent, 10) || 0;
  let liked = false;
  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  const COLORS = ['#ff4d6d', '#ffa1b5', '#ffd166', '#8ac6ff', '#c08bff'];

  // 放射状に粒子を生成
  const burst = () => {
    if (reduce) return;
    const N = 10;
    for (let i = 0; i < N; i++) {
      const s = document.createElement('span');
      s.className = 'spark';
      const angle = (Math.PI * 2 * i) / N + Math.random() * 0.4;
      const dist = 26 + Math.random() * 22;
      s.style.setProperty('--dx', `${Math.cos(angle) * dist}px`);
      s.style.setProperty('--dy', `${Math.sin(angle) * dist}px`);
      s.style.background = COLORS[i % COLORS.length];
      s.addEventListener('animationend', () => s.remove());
      btn.appendChild(s);
    }
  };

  btn.addEventListener('click', () => {
    liked = !liked;
    btn.classList.toggle('is-active', liked);
    btn.setAttribute('aria-pressed', String(liked));
    count.textContent = String(base + (liked ? 1 : 0));
    if (liked) burst(); // 「いいね」した瞬間だけ弾ける
  });
})();

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

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

# 追加してほしい効果
いいねハートバースト(マイクロインタラクション)
クリックでハートが弾み、放射状に色とりどりの粒子が飛び散る「いいね」ボタン。SNSやレビューのリアクションUIに使えます。

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

# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- いいねハートバースト: クリックで弾けて粒子が飛び散る -->
<div class="stage">
  <button class="like" type="button" aria-pressed="false" aria-label="いいね">
    <svg class="like__heart" viewBox="0 0 24 24" aria-hidden="true">
      <path d="M12 21s-7.5-4.6-10-9.2C.3 8.6 1.8 5 5.2 5c2 0 3.3 1.1 4 2.2C9.9 6.1 11.2 5 13.2 5c3.4 0 4.9 3.6 3.2 6.8C19.5 16.4 12 21 12 21z"/>
    </svg>
    <span class="like__count">128</span>
  </button>
  <p class="hint">クリックして「いいね」</p>
</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: linear-gradient(135deg, #fff1f3 0%, #ffe3ec 100%);
}

.stage {
  display: grid;
  place-items: center;
  gap: 18px;
}

/* いいねボタン */
.like {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 14px 26px;
  border: none;
  border-radius: 999px;
  cursor: pointer;
  background: #fff;
  box-shadow: 0 10px 26px rgba(255, 87, 124, .18);
  transition: transform .12s ease, box-shadow .25s ease;
  -webkit-tap-highlight-color: transparent;
}
.like:active { transform: scale(.95); }

.like__heart {
  width: 30px;
  height: 30px;
  fill: #d7dbe3;
  transition: fill .2s ease, transform .25s cubic-bezier(.68,-0.55,.27,1.55);
}

/* 押下時の弾むスケール(JSが付与) */
.like.is-active .like__heart {
  fill: #ff4d6d;
  animation: heart-pop .45s cubic-bezier(.34,1.56,.64,1);
}
@keyframes heart-pop {
  0%   { transform: scale(.2); }
  45%  { transform: scale(1.35); }
  100% { transform: scale(1); }
}

.like__count {
  font-size: 18px;
  font-weight: 700;
  color: #44424d;
  font-variant-numeric: tabular-nums;
}
.like.is-active .like__count { color: #ff4d6d; }

/* 飛び散る粒子(JSで生成) */
.spark {
  position: absolute;
  top: 50%;
  left: 27px; /* ハート中心付近 */
  width: 8px;
  height: 8px;
  border-radius: 50%;
  pointer-events: none;
  transform: translate(-50%, -50%);
  animation: spark-fly .6s ease-out forwards;
}
@keyframes spark-fly {
  0%   { opacity: 1; transform: translate(-50%, -50%) translate(0, 0) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) translate(var(--dx), var(--dy)) scale(.2); }
}

.hint {
  margin: 0;
  font-size: 13px;
  color: #9a879a;
}

@media (prefers-reduced-motion: reduce) {
  .like.is-active .like__heart { animation: none; }
  .spark { animation-duration: .01ms; }
}

【JavaScript】
// いいねハートバースト: トグルで色変化+粒子を放射状に飛ばす
(() => {
  const btn = document.querySelector('.like');
  const count = document.querySelector('.like__count');
  if (!btn || !count) return; // null安全

  const base = parseInt(count.textContent, 10) || 0;
  let liked = false;
  const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
  const COLORS = ['#ff4d6d', '#ffa1b5', '#ffd166', '#8ac6ff', '#c08bff'];

  // 放射状に粒子を生成
  const burst = () => {
    if (reduce) return;
    const N = 10;
    for (let i = 0; i < N; i++) {
      const s = document.createElement('span');
      s.className = 'spark';
      const angle = (Math.PI * 2 * i) / N + Math.random() * 0.4;
      const dist = 26 + Math.random() * 22;
      s.style.setProperty('--dx', `${Math.cos(angle) * dist}px`);
      s.style.setProperty('--dy', `${Math.sin(angle) * dist}px`);
      s.style.background = COLORS[i % COLORS.length];
      s.addEventListener('animationend', () => s.remove());
      btn.appendChild(s);
    }
  };

  btn.addEventListener('click', () => {
    liked = !liked;
    btn.classList.toggle('is-active', liked);
    btn.setAttribute('aria-pressed', String(liked));
    count.textContent = String(base + (liked ? 1 : 0));
    if (liked) burst(); // 「いいね」した瞬間だけ弾ける
  });
})();

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

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