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

一行でコピペOK ― CSS カード ホバー アニメーション9選

css カード ホバーとは、カード型のUIコンポーネントがマウスやフォーカスを受け取った瞬間だけ、浮く・光る・傾くといった短い変化を見せる仕掛けです。今回は9種類を選び、7つはCSSのtransformとtransitionだけで、残り2つはJavaScriptを10行前後添えて実装しました。

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

並び順は人気順ではなく、カードを選ぶ人の視線がたどる順番に合わせました。まず存在を主張する2つ(01・02)、次に傾きと内容で層を見せる3つ(03〜05)、css ホバー グラデーション で表面の縁を変える1つ(07)、押せると伝える2つ(06・08)を経て、最後に実際に押された瞬間へ答える1つ(09)で終わります。コードは要点だけを載せていて、完成した9本のファイルはzipにまとめてあります。カードではなく会話画面を整えるならチャット 吹き出し css アニメーション、07のグラデーション枠を文字側にも流したいならCSS テキスト グラデーション アニメーションが続きの置き場所になります。

01浮き上がり+影

どう動くか

カードにカーソルを重ねると8pxほど上へ浮き上がり、box-shadowで影が一段濃くなります。クリックする前からグリッドの中で真っ先に目に入るようになります。

どこで使うか

商品カードの一覧やブログ記事一覧のカードに向いています。

浮き上がり+影
新しいタブで開く

01 · 浮き上がり+影 — 計測 累積面積 11.669% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

transform: translateYbox-shadow 확대transition
.card {
  transform: translateY(0);
  transition:
    transform .2s $ease-spring,
    box-shadow .2s $ease-spring;
}

.card:hover,
.card:focus-visible,
.card:active {
  transform: translateY(-8px);
  box-shadow: $shadow-raised;
}

02枠を光が回る

どう動くか

カードの枠の内側でconic-gradientの光が2秒に一度ぐるりと回り続け、触れる前から静かに動いているように見えます。角度は@propertyで登録した--angleを0degから360degへkeyframesで進めているので、値が飛ばずになめらかに繋がります。

どこで使うか

プレミアムな料金プランカードやAI機能紹介カードに向いています。

枠を光が回る
新しいタブで開く

02 · 枠を光が回る — 計測 累積面積 1.778% · 動いたフレーム 23/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

conic-gradient@property angleopacity
@property --angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.card__ring {
  background: conic-gradient(
    from var(--angle),
    transparent 0deg, $color 40deg, transparent 100deg
  );
  opacity: 0;
}

.card:hover .card__ring,
.card.is-demo .card__ring {
  opacity: 1;
  animation: spin 2s linear infinite;
}

@keyframes spin { to { --angle: 360deg; } }

033Dチルト視差

どう動くか

カーソルの位置に応じてrotateXとrotateYでカードが傾き、背景レイヤーだけが逆方向へわずかにずれて層が分かれて見えます。座標の計算はJavaScript数行だけが担い、CSS側はその値を変数として受け取るだけです。

どこで使うか

機能紹介カードやポートフォリオのプロジェクトカードに向いています。

3Dチルト視差
新しいタブで開く

03 · 3Dチルト視差 — 計測 累積面積 11.272% · 動いたフレーム 18/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

perspectiverotateX/rotateYJS 좌표
card.addEventListener('pointermove', (e) => {
  const r = card.getBoundingClientRect();
  const px = (e.clientX - r.left) / r.width - .5;
  const py = (e.clientY - r.top) / r.height - .5;
  card.style.setProperty('--rx', `${-py * 16}deg`);
  card.style.setProperty('--ry', `${px * 20}deg`);
  card.style.setProperty('--pz', `${px * -10}px`);
});

04内容がスライドアップ

どう動くか

画像の下に隠れている説明パネルが、ホバーすると画像の上へtranslateYでスライドアップして現れます。カードの高さ自体は変わらないまま、情報だけが一枚追加されます。

どこで使うか

画像中心の商品カードやギャラリーのサムネイルカードに向いています。

内容がスライドアップ
新しいタブで開く

04 · 内容がスライドアップ — 計測 累積面積 11.14% · 動いたフレーム 18/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

transform: translateYoverflow: hiddenopacity
.card__panel {
  transform: translateY(70%);
  transition: transform .35s $ease-spring;
}

.card:hover .card__panel,
.card.is-demo .card__panel {
  transform: translateY(0);
}

05背景画像が拡大

css ホバー 拡大 の作り方

カードを覆う背景がホバーで1.12倍に拡大し、overflow: hiddenが角丸の外へのはみ出しを切り取ります。滑らかなグラデーションだけでは拡大が伝わりにくいため、輪郭のはっきりした太陽と丘の形を重ねています。

どこで使うか

旅行先の紹介カードやブログのカバーカードに向いています。

背景画像が拡大
新しいタブで開く

05 · 背景画像が拡大 — 計測 累積面積 7.676% · 動いたフレーム 20/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

transform: scaleoverflow: hiddenborder-radius 유지
.card { overflow: hidden; }

.card__bg {
  transform: scale(1);
  transition: transform .5s $ease-out;
}

.card:hover .card__bg,
.card.is-demo .card__bg {
  transform: scale(1.12);
}

06アイコンが弾む

どう動くか

カード右上の丸いアイコンバッジが、ホバーすると2回小さく弾んでクリックできることを伝えます。keyframesで上下にtranslateYさせるだけで、cubic-bezierの効いた動きがボールの弾む感触を作ります。

どこで使うか

ブログカードの「続きを読む」矢印やリンクカードに向いています。

アイコンが弾む
新しいタブで開く

06 · アイコンが弾む — 計測 累積面積 2.056% · 動いたフレーム 18/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

@keyframes bouncecubic-beziertransform: translateY
.card__icon { transform: translateY(0); }

.card:hover .card__icon,
.card:focus-visible .card__icon,
.card:active .card__icon {
  animation: bounce .3s $ease-pop 2;
}

@keyframes bounce {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-8px); }
}

07グラデーション枠

どう動くか

普段は薄いグレーの枠のカードが、ホバーすると2色のグラデーション枠に変わって縁取ります。mask-compositeで内側だけを切り抜いた層のopacityを0から1に変えるだけなので、レイアウトはまったく動きません。

どこで使うか

料金比較カードやおすすめを強調したいカードに向いています。

グラデーション枠
新しいタブで開く

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

mask-compositelinear-gradientopacity
.card__border {
  background: linear-gradient(135deg, $color, $subject-cream);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0;
  transition: opacity .35s $ease-out;
}

.card:hover .card__border,
.card.is-demo .card__border {
  opacity: 1;
}

08カーソルスポットライト

どう動くか

カーソル座標をJavaScript数行で読み取り--mxと--myというCSS変数に渡すと、radial-gradientの光がカーソルに追従してカード表面を照らします。--spという数値も@propertyで登録してあるので、光の強さがふっと現れて自然に消えます。

どこで使うか

ダークテーマのカードやインタラクティブな機能カードに向いています。

カーソルスポットライト
新しいタブで開く

08 · カーソルスポットライト — 計測 累積面積 9.937% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

pointermoveCSS 변수radial-gradient
card.addEventListener('pointermove', (e) => {
  const r = card.getBoundingClientRect();
  const mx = (e.clientX - r.left) / r.width * 100;
  const my = (e.clientY - r.top) / r.height * 100;
  card.style.setProperty('--mx', `${mx}%`);
  card.style.setProperty('--my', `${my}%`);
  card.style.setProperty('--sp', 1);
});

09押した反応

どう動くか

クリック可能なカードを実際に押すとscaleが0.97まで縮み、影も一段浅くなって指先で触れた感触を伝えます。ホバーだけでは反応せず、押している間だけ動きます。

どこで使うか

選択できるカード型のラジオボタンやタッチ操作のモバイルカードに向いています。

押した反応
新しいタブで開く

09 · 押した反応 — 計測 累積面積 2.287% · 動いたフレーム 17/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

:activetransform: scalebox-shadow 축소
.card {
  transform: scale(1);
  transition:
    transform .1s $ease-out,
    box-shadow .1s $ease-out;
}

.card:active {
  transform: scale(.97);
  box-shadow: $shadow-press;
}

どこで崩れるか — 落とし穴

@property がないと補間できない

9つの中で02と08はどちらも@propertyでカスタムプロパティの型を登録しています。最初はこれを省いて--angleや--mx・--myをただのCSS変数として書いたところ、02の光は回転させたつもりが一コマも動かず一瞬で消え、08のスポットライトも位置がなめらかに追従せず飛び飛びに切り替わるだけでした。MDNの@propertyの解説にあるとおり、ブラウザは<angle>や<percentage>として型を教えられない限りカスタムプロパティを補間できず、アニメーションの途中の値を作れません。

Safari だけ枠が消えない

2つ目は07のグラデーション枠を組んでいるときに踏んだ落とし穴です。標準のmask-composite: excludeだけを書いてSafariで確かめると枠がいつまでも透明にならず、正方形のグラデーションがカード全体を覆ったままでした。caniuseのmask-composite対応表にあるとおりSafariは長らく接頭辞付きの-webkit-mask-compositeしか読めず、しかもキーワードはexcludeではなくxorだったため、両方を並べて書いてからSafariでも枠が正しく切り抜かれるようになりました。そもそも枠をマスクで抜かずに済ませる方向もあり、ネオブルータリズム UI キットの枠線は不透明な線を直接引くだけなので、この2つの語彙のずれに巻き込まれません。修正済みの9本のファイルを開く合言葉は45c9tna8で、下のカードから取り出せます。

なめらかすぎて測れない

3つ目は05の背景拡大を作っているときに見つけた発見です。角度だけが違うなめらかなlinear-gradientだけを背景に敷いてscale(1.12)を掛けたところ、レンダリングして測定すると24フレームすべてで隣り合うピクセルの色の差が閾値の12を超えず、累積面積は0.00%のまま「動いていないカード」と判定されました。MDNのlinear-gradient()の解説にあるとおり色がなめらかに混ざる帯は隣り合うピクセルの差が小さくなりがちで、境界のはっきりした太陽と丘の形を重ねてから同じ倍率で測り直すと、強度41.3・累積面積0.68%まで上がり正常に通過しました。

アクセシビリティ

動きを減らす設定では

9つともprefers-reduced-motion: reduceでは動きだけを止め、最後の見た目は残します。01は浮かばず影だけが濃いまま、02は光が回らず薄く固定、03は傾かず:focus-visibleで既定の角度だけを見せ、04はパネルが最初から開いたまま、05は拡大せず静止、06はバッジが跳ねず、07は枠が常にうっすら見えたまま、08は光が灯った状態で止まり、09は影だけが浅くなります。

キーボードとタッチ

:hoverだけでなく:focus-visibleと:activeにも同じ最終状態を与えているので、キーボード操作やタッチ操作でもマウス操作と同じ結果が届きます。カード以外のCSSアニメーションはカテゴリー一覧に、このサイトの成り立ちは紹介ページにまとめています。

FAQ

08のスポットライトはタッチ画面でもカーソルに追従しますか?

いいえ、pointermoveはタッチでは座標を継続して渡さないため、08は--spが既定値の0のまま止まります。代わりに:focus-visibleと:activeでカード中央に固定のスポットライトを灯し、指で押しても反応だけは残るようにしています。

css ホバー アニメーション を1つの画面に9つ並べても大丈夫ですか?

並べても構いませんが、02と08のように回り続けたり追従し続けたりする演出は一画面に1つか2つにとどめるのが無難です。残りの7つはホバー・フォーカス・プレスといったユーザーの操作に紐づいているので、何枚並べても画面がうるさくなりすぎることはありません。カードの中に切り替えスイッチを置くなら、09の押した反応とCSS トグルスイッチのdurationをそろえておくと動きがぶつかりません。

Reactの03と08はカーソル座標をどう扱っていますか?

どちらのコンポーネントもuseRefでカード要素をつかみ、useCallbackで包んだpointermoveハンドラーの中でstyle.setPropertyを使ってCSS変数へ直接書き込みます。stateを経由しないので、座標が変わるたびにコンポーネント全体が再レンダリングされることはありません。

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

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