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

CSS ハート アニメーション 9選 — 一行でコピペOK

css ハート アニメーションを9種類まとめました。いいねボタンのハートから星評価、拍手カウントや絵文字リアクションまで、押した瞬間に画面がはっきり反応するものだけを選び、ほとんどはtransformとopacityだけで動き、05だけwidthで割合を示します。

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

並べた順は人気ではなく、押した直後に何が起きるかです。まずハート自体が跳ねる2つ(01・02)、隣の数字が連動する1つ(03)、値を選ぶ星評価2つ(04・05)、押すたびに増える1つ(06)、あとで見返すための保存1つ(07)、確認の合図1つ(08)、最後は複数から1つを選ぶリアクション(09)という並びです。コードは動きを作る核だけを抜き出していて、完成した9ファイルはzipを開かないと出てきません。

01ハートがポンと膨らむ

「いいね アニメーション css」の基本形

いいねのラベルを押すと、内側に重ねた塗りつぶしハートがopacity 0・scale .4から1倍まで一気に伸び、回転も-8度から0度へ戻ります。裏にあるのは本物のチェックボックスなので、ページを読み込み直しても押した状態が消えません。投稿のいいねボタンや商品のお気に入りアイコンに向いています。

ハートがポンと膨らむ
新しいタブで開く

01 · ハートがポンと膨らむ — 計測 累積面積 1.06% · 動いたフレーム 20/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

::before/::after 하트:checked0.22초

どこに置くか

.heart__shape--fill {
  color: $color;
  opacity: 0;
  transform: scale(.4) rotate(-8deg);
  transition: opacity $duration $easing, transform $duration $easing;
}
.heart__input:checked ~ .heart__icon .heart__shape--fill {
  opacity: 1;
  transform: scale(1) rotate(0deg);
}
@keyframes heart-pop-loop {
  35%      { opacity: 1; transform: scale(1.15) rotate(6deg); }
  55%, 80% { opacity: 1; transform: scale(1) rotate(0deg); }
}

02パーティクルが弾ける

8つの点が散る角度

チェックが入った瞬間、ハートの周りに置いた8つの点がCSSカスタムプロパティ--iで角度を割り振られ、それぞれ違う向きへtranslateYとopacityで飛び散って消えます。画像は一枚も使わず、span8個とclip-pathで描いた小さなハートだけで組みました。SNSのリアクションボタンや、アプリで最初のいいねを促す場面に向いています。

パーティクルが弾ける
新しいタブで開く

02 · パーティクルが弾ける — 計測 累積面積 1.614% · 動いたフレーム 18/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

--i 커스텀 속성translate+opacity스태거

向いている場面

.burst__dot {
  position: absolute; top: 50%; left: 50%;
  width: 5px; height: 5px; margin: -2.5px 0 0 -2.5px;
  border-radius: 50%; background: $color;
  transform: rotate(calc(var(--i) * 45deg)) translateY(0) scale(1);
}
.burst__input:checked ~ .burst__icon .burst__dot {
  animation: particle-fly $duration $easing forwards;
  animation-delay: calc(var(--i) * 20ms);
}
@keyframes particle-fly {
  100% { opacity: 0; transform: rotate(calc(var(--i) * 45deg)) translateY(-34px) scale(.2); }
}

03いいねカウント連動

数字が連動する仕組み

チェックのオン・オフが切り替わるたび、change イベントを拾ったJS数行が隣の数字を1増減させ、変わった直後の数字だけscaleでポンと弾みます。CSSだけでは文字そのものを書き換えられないため、ここだけJSに任せています。

いいねカウント連動
新しいタブで開く

03 · いいねカウント連動 — 計測 累積面積 0.179% · 動いたフレーム 10/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

JS 카운트scale pop:checked

CSSだけでは足りない理由

var w = document.querySelector(".like"), inp = w.querySelector(".like__input"), num = w.querySelector(".like__num");
function sync() {
  num.textContent = inp.checked ? "13" : "12";
  num.classList.remove("is-pop"); void num.offsetWidth; num.classList.add("is-pop");
}
inp.addEventListener("change", function () { w.classList.remove("is-demo"); sync(); });

04星の塗りつぶし

ラジオを逆順に並べる理由

5個のラジオを5・4・3・2・1の逆順に並べ、チェックされたラジオより後ろの星をすべて~セレクタで塗りつぶし、選んだ星だけさらにscaleで弾ませて選択を伝えます。5つの星はfieldsetとlegendでひとつの評価グループにまとめてあります。レビューの星評価入力や商品の満足度評価に向いています。選んだ瞬間その星の上からミニ星4個が弾け、それぞれ質量(0.6〜1.4)を持つためrequestAnimationFrame内の準陰的オイラー積分で減衰(-k/m·v — 重いほど減速しにくい)と重力を受けながら飛んで止まります。

星の塗りつぶし
新しいタブで開く

04 · 星の塗りつぶし — 計測 累積面積 2.926% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

~ 형제 선택자fieldset/legendscale 바운스

レビュー入力での使いどころ

.rating__stars { display: flex; flex-direction: row-reverse; }
.rating__input:checked ~ .rating__star { color: $color; }
.rating__input:checked + .rating__star { animation: star-pick $duration $easing; }
@keyframes star-pick {
  45%  { transform: scale(1.35); }
  100% { transform: scale(1); }
}

05半星の幅クリップ表示

入力ではなく読み取り専用

これは入力ではなく、平均評価だけを示す読み取り専用バッジです。同じ星5個を2枚重ね、上の層だけをwidthでカスタムプロパティ--ratingから計算した割合まで見せることで、小数点付きの評価を滑らかに塗り分けています。商品一覧の平均評価やレビュー要約バッジに向いています。塗りが先端(--pctの位置)に達した瞬間、その位置からミニ星6個が弾け、これも質量ごとの減衰(-k/m·v)と質量によらない重力をrequestAnimationFrameの積分で受けて散らばります。

半星グラデーション表示
新しいタブで開く

05 · 半星グラデーション表示 — 計測 累積面積 1.199% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

--rating 커스텀 속성overflow 폭 클리핑루프 리빌

商品一覧での使いどころ

.avg__fill {
  position: absolute; inset: 0;
  width: 0%; overflow: hidden; color: $color;
}
@keyframes reveal-fill {
  55%, 80% { width: var(--pct); }
  100%     { width: var(--pct); }
}
.avg__fill { animation: reveal-fill $dur-loop $easing infinite; }

06拍手カウント

押すたびに増える数字

押すたびに数字が増えるボタンで、クリックごとにアイコンがscaleとrotateで軽く弾み、「+1」の文字がtranslateYで上へ流れながらopacityで消えます。Medium風の拍手ボタンやフィードバックウィジェットに向いています。拍手のたびアイコンの中心からミニ星5個も短く弾け、軽い質量(0.6〜1.2)と速い減衰により0.6秒ほどで落ちて消えるので拍手のリズムに合わせた動きになります。

拍手カウント
新しいタブで開く

06 · 拍手カウント — 計測 累積面積 0.886% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

반복 클릭translateY+opacityJS 카운트

Medium風の拍手ボタンに

@keyframes clap-bounce {
  40%  { transform: scale(1.3) rotate(-12deg); }
  100% { transform: scale(1) rotate(0deg); }
}
@keyframes clap-plus {
  0%   { opacity: 1; transform: translateY(0); }
  100% { opacity: 0; transform: translateY(-16px); }
}

07ブックマークの折り込み

折り込みの基準点

チェックするとリボン型のブックマークが上からscaleYで塗りつぶされ、右上角の小さな三角形がその角を基準にrotateとscaleで開き、紙が折れて戻ったように見えます。記事の保存ボタンやあとで読むリストへの追加に向いています。折り込みが完成した瞬間、その角からダイヤモンド型の紙片6個が散り、それぞれ質量(0.7〜1.6)を持つため重いものは慣性でより遠くへ飛んでから減衰(-k/m·v)と重力に抑えられて止まります。

ブックマークの折り込み
新しいタブで開く

07 · ブックマークの折り込み — 計測 累積面積 0.758% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

clip-path 리본transform-origin 모서리rotate+scale

記事の保存ボタンに

.bookmark__fold {
  clip-path: polygon(100% 0, 0 0, 100% 100%);
  transform: scale(0) rotate(-90deg);
  transform-origin: 100% 0;
  opacity: 0;
  transition: opacity $duration $easing, transform $duration $easing;
}
.bookmark__input:checked ~ .bookmark__icon .bookmark__fold {
  opacity: 1; transform: scale(1) rotate(0deg);
}

08親指アイコンのバウンス

グレースケールから色が戻るまで

チェックされる前はfilter: grayscaleで色を落としてあり、チェックされると色が戻ると同時に、親指のアイコンが-20度から5度を経て0度に戻るまでscaleも一緒に伸び縮みします。回答の「役に立った」ボタンやレビューの「参考になった」表示に向いています。着地のオーバーシュートの瞬間にはアイコンからミニ星4個と点2個の衝撃スパークが跳ね、軽い質量と速い減衰(k=4.0)で短く閃いて消えるため力が込もった感触になります。

親指アイコンのバウンス
新しいタブで開く

08 · 親指アイコンのバウンス — 計測 累積面積 1.192% · 動いたフレーム 20/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

rotate+scale:checked손짓 이징

「役に立った」ボタンに

.thumb__icon {
  filter: grayscale(1) opacity(.55);
  transition: filter $duration $easing;
}
.thumb__input:checked ~ .thumb__icon {
  filter: grayscale(0) opacity(1);
  animation: thumb-bounce $duration $easing;
}
@keyframes thumb-bounce {
  30%  { transform: scale(1.25) rotate(-20deg); }
  60%  { transform: scale(1.1) rotate(5deg); }
}

09絵文字リアクション

絵文字を1つだけ選ぶ仕組み

4つの絵文字はラジオで1つだけ選べるグループにまとめ、:has()でグループ内に選んだものがあるかを判定して全体をわずかに沈め、選んだ絵文字だけopacityとscaleで再び持ち上げます。チャットメッセージのリアクションや投稿への感情リアクションに向いています。選んだ瞬間その絵文字からミニハート5個が上へ散り、ハートごとに質量(0.6〜1.4)が違うため軽いものは早く落ち、重いものは慣性で長く浮かびます(requestAnimationFrame積分)。

絵文字リアクション
新しいタブで開く

09 · 絵文字リアクション — 計測 累積面積 4.024% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

fieldset/radio:has()scale+opacity

チャットのリアクションに

.react__emoji {
  transition: opacity $duration $easing, transform $duration $easing;
}
.react:has(.react__input:checked) .react__emoji {
  opacity: .4; transform: scale(.9);
}
.react__input:checked + .react__emoji {
  opacity: 1; transform: scale(1.3);
}

どこで壊れるか — カスタムプロパティの罠

カスタムプロパティは補間されない

05の平均評価バッジを最初に組んだときは、--fill: 0%から--fill: var(--pct)へ動かすキーフレームを、カスタムプロパティそのものに対して書いていました。ところが実際に計測すると24フレーム中たった1フレームしか値が変わっておらず、星は滑らかに満ちるどころか一瞬で切り替わっているだけでした。MDNの@propertyのページにあるとおり、カスタムプロパティは型を登録しない限り補間の対象になりません。型を登録せずに直す方法は、widthのようにもとから補間される標準プロパティへcalc(var(--rating) / 5 * 100%)という計算値を渡すことで、05はこの形に書き直してから24フレーム中13フレームが動く滑らかなアニメーションになりました。

:has() は上乗せとして使う

もう一つ、04の星の塗りつぶしと09の絵文字リアクションは:has()という比較的新しいセレクタに頼っています。対応状況はcaniuse.comの:has()のページにまとまっていて、ChromeとEdgeは105以降、Safariは15.4以降、Firefoxは121以降で動き、それより古いブラウザでは09の沈み込みだけが起きず、絵文字を選ぶ操作自体は変わらず機能します。ここまで直した9つのファイルを開くには8ayu345cという文字列をそのまま入力する必要があり、下のカードからzipを受け取ってください。

アクセシビリティ

動きを止めても残るもの

9つともprefers-reduced-motion: reduceでは跳ねたり弾けたりする動きを止め、チェックや選択という最終的な状態の色だけを残します。01・02・07・08は登場のアニメーションをやめてチェック済みの最終形をそのまま出し、05はリビール用のanimationを切って目標のwidthを最初から固定します。

@media (prefers-reduced-motion: reduce) {
  .heart__shape--fill { transition: opacity 1ms; }
  .avg__fill { animation: none !important; width: var(--pct); }
  .thumb__icon { transition: filter 1ms; }
}

星グループのラベル付け

04と09は星や絵文字をそれぞれfieldsetとlegendでひとつのグループとしてまとめ、各ラジオに読み上げ用のaria-labelを添えています。

<fieldset class="rating">
  <legend class="rating__legend">評価</legend>
  <input type="radio" name="rating" id="r5" class="rating__input" aria-label="5点">
  <label for="r5" class="rating__star" aria-label="5点">★</label>
</fieldset>

ボタンに置き換えるとき

「css ボタン 押したら変わる」形をチェックボックスではなくボタンで作るなら、押すたびに値をJSで書き換えるaria-pressed属性が必要です。

<button type="button" class="thumb" aria-pressed="false">
  <span class="thumb__icon" aria-hidden="true">👍</span>
</button>

06の拍手ボタンは押すたびに増える数字をaria-live="polite"で知らせ、画面を見ていない人にも変化が届くようにしています。

次に読む

ハートや星以外のCSSアニメーションを探すならCSS記事の一覧に、このブログが何を配っているサイトかはこのサイトについてにまとめてあります。

FAQ

「css ボタン アニメーション」を9つとも同じ画面に置いても大丈夫ですか?

カードごとに繰り返すいいねハートや星評価は、一覧の中に何個並んでいても問題ありません。ただし06の拍手カウントや09の絵文字リアクションのように押すたびに画面が大きく反応するものは、同じ画面に1つだけ置くほうが視線が散りません。複数が同時に弾むと、どこを見ればいいか分からなくなります。

Reactのコンポーネントでも同じ動きになりますか?

zip内のreact/以下にある各コンポーネントは、チェックや選択の状態をuseStateで持ち、色や速さを決める値だけをCSS側の変数へ渡しています。04の星評価と09のリアクションは選んだ値をonChangeで親コンポーネントへ返し、05は入力ではなくratingというprop一つで表示だけを受け取る読み取り専用の作りです。

チェックボックスをbuttonに置き換えても動きますか?

はい、オン・オフの2状態だけならaria-pressedを持つbuttonに置き換えても同じ見た目を作れます。ただしラベルと自動でつながるチェックボックスと違い、buttonは状態が変わるたびにaria-pressedの値を自分でJSから更新しないと、スクリーンリーダーには変化が伝わりません。

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

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