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

マウス カーソル エフェクト 9選 — コピペOK

マウス カーソル エフェクトとは、カーソルの移動やクリック・ドラッグ・スクロールに合わせて画面が反応する小さなインタラクションのことです。カーソルを動かしただけなのに、ボタンが寄ってきたりカードが立体的に傾いたりするものもここに含まれます。

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

以下の9つは人気順ではなく、マウス ホバー エフェクトを含め、ポインタが画面の上で実際に出会う順番で並んでいます。じっとしていて、リンクの上で形が変わり(01)、ボタンに近づくと引き寄せられ(02)、カードの上を通ると光が追いかけて傾き(03・04)、画面を手で引っぱり(05)、右クリックでメニューが出て(06)、文章をなぞると色が付き(07)、ページを読み進めるとリングが満ちていき(08)、最後にグリッドを一巡するとキャプションが現れます(09)。マウスが無い画面へ同じ動きを指で移した版はスマホ ジェスチャー操作にあり、ポインタの軌跡をそのまま絵として残す側はcanvas 手書きサインと描画UIへ続きます。

01カーソル形状モーフ

カーソル エフェクト cssで何が見えるか

リンクにカーソルを乗せると小さな点が矢印入りの青い円に広がり、離れると点に戻ります。

カーソル形状モーフ
新しいタブで開く

01 · カーソル形状モーフ — 計測 累積面積 3.019% · 動いたフレーム 16/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

pointermove translatedata-cursor 속성pointer:coarse 터치 제외

コードのどこを見るか

pointermove で点の位置を動かし、pointerenter・pointerleave で形を切り替えます。

area.addEventListener('pointermove', function (e) {
  var r = area.getBoundingClientRect();
  cursor.style.transform =
    'translate(' + (e.clientX - r.left) + 'px,' +
    (e.clientY - r.top) + 'px)';
});
link.addEventListener('pointerenter', function () {
  cursor.classList.add('is-link');
});
link.addEventListener('pointerleave', function () {
  cursor.classList.remove('is-link');
});

02マグネティックプルボタン

画面で起きること

ボタン周辺にカーソルを近づけるとその方向へわずかに引き寄せられ、離れると元の位置に戻ります。

マグネティックプルボタン
新しいタブで開く

02 · マグネティックプルボタン — 計測 累積面積 6.596% · 動いたフレーム 11/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

pointermove 거리×계수transform: translatepointerleave 리셋

コードの読み方

ヒット領域の中心からの距離に係数(0.4)を掛けて translate の値を作ります。

zone.addEventListener('pointermove', function (e) {
  var r = zone.getBoundingClientRect();
  var dx = e.clientX - (r.left + r.width / 2);
  var dy = e.clientY - (r.top + r.height / 2);
  btn.style.transform =
    'translate(' + dx * 0.4 + 'px,' + dy * 0.4 + 'px)';
});
zone.addEventListener('pointerleave', function () {
  btn.style.transform = '';
});

03スポットライトカードリビール

どんな動きか

カード上でカーソルを動かすと丸い光がついてきて、光が通った場所の薄い文字が明るく浮かび上がります。

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

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

mix-blend-mode: soft-lightpointermove translatehover:none 대체

どこを変えるか

mix-blend-mode: soft-light で光を背景に混ぜ、screen で文字を明るくします。

card.addEventListener('pointermove', function (e) {
  var r = card.getBoundingClientRect();
  spot.style.transform =
    'translate(' + (e.clientX - r.left) + 'px,' +
    (e.clientY - r.top) + 'px)';
});
card.addEventListener('pointerleave', function () {
  spot.style.transform = '';
});

043Dチルトカード

何が見えるか

カード上のカーソル位置に応じてカードが立体的に傾き、斜めの光沢も一緒に流れます。

3Dチルトカード
新しいタブで開く

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

perspective + rotateX/rotateYbackground-position 스윕신뢰감 있는 밝은 톤

コードのどこを見るか

カーソル座標を0〜1の比率に変換し、rotateX・rotateY の角度を計算します。

wrap.addEventListener('pointermove', function (e) {
  var r = wrap.getBoundingClientRect();
  var px = (e.clientX - r.left) / r.width;
  var py = (e.clientY - r.top) / r.height;
  var rx = (0.5 - py) * 6 * 2;
  var ry = (px - 0.5) * 6 * 2;
  card.style.transform =
    'perspective(600px) rotateX(' + rx + 'deg) rotateY(' + ry + 'deg)';
});

05ドラッグスクロールギャラリー

画面で起きること

タイルを手でつかんで引くようにドラッグすると横にスクロールし、離した位置で止まります。

ドラッグスクロールギャラリー
新しいタブで開く

05 · ドラッグスクロールギャラリー — 計測 累積面積 13.938% · 動いたフレーム 20/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

scrollLeft 직접 조작overflow-x: auto 네이티브 터치cursor: grab/grabbing

コードの読み方

transform ではなく本物の scrollLeft を動かすので、タッチでもネイティブスクロールがそのまま続きます。

vp.addEventListener('pointerdown', function (e) {
  dragging = true;
  startX = e.clientX;
  startScroll = vp.scrollLeft;
  vp.setPointerCapture(e.pointerId);
});
vp.addEventListener('pointermove', function (e) {
  if (!dragging) return;
  vp.scrollLeft = startScroll - (e.clientX - startX);
});

06カスタム右クリックメニュー

どんな動きか

エリアを右クリックするとブラウザ標準メニューの代わりに、コピー・編集・削除アイコンメニューがクリック位置に弾むように現れます。

カスタム右クリックメニュー
新しいタブで開く

06 · カスタム右クリックメニュー — 計測 累積面積 13.451% · 動いたフレーム 11/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

contextmenu preventDefault좌표 클램프Escape·바깥 클릭 닫기

どこを変えるか

座標はエリアの外に出ないようクランプしています。

pad.addEventListener('contextmenu', function (e) {
  e.preventDefault();
  var r = pad.getBoundingClientRect();
  var x = Math.min(e.clientX - r.left, r.width - 132);
  var y = Math.min(e.clientY - r.top, r.height - 112);
  menu.style.left = x + 'px';
  menu.style.top = y + 'px';
  pad.classList.add('is-open');
});

07テキスト選択のマーカー色

何が見えるか

文章をドラッグ選択すると、ブラウザの青色ではなく指定したマーカー色で塗られ、選択範囲の上にコピーバッジが現れます。

テキスト選択のマーカー色
新しいタブで開く

07 · テキスト選択のマーカー色 — 計測 累積面積 2.017% · 動いたフレーム 15/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

::selection 색 지정selectionchange 이벤트getBoundingClientRect 배지 위치

コードのどこを見るか

色は ::selection 一つで決め、バッジの位置は selectionchange で取得します。

::selection {
  background: #ff9ebb;
  color: #17141a;
}
document.addEventListener('selectionchange', function () {
  var sel = window.getSelection();
  if (sel.isCollapsed) return;
  var r = sel.getRangeAt(0).getBoundingClientRect();
  badge.style.left = r.left + r.width / 2 + 'px';
  badge.style.top = r.top - 28 + 'px';
  badge.classList.add('is-shown');
});

08スクロール進捗カーソルリング

画面で起きること

内部をスクロールする間、カーソルに追従する小さなリングが進捗分だけ満ちていき、最後まで読むとチェックに変わります。

スクロール進捗カーソルリング
新しいタブで開く

08 · スクロール進捗カーソルリング — 計測 累積面積 3.108% · 動いたフレーム 22/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

stroke-dashoffsetscrollTop/scrollHeight 비율pointermove translate

コードの読み方

リングの stroke-dashoffset をスクロール比率だけ減らします。

box.addEventListener('scroll', function () {
  var max = box.scrollHeight - box.clientHeight;
  var pct = max > 0 ? box.scrollTop / max : 0;
  ring.style.setProperty('--dash', (94.2 * (1 - pct)).toFixed(1));
  box.classList.toggle('is-done', pct > 0.98);
});

09ホバーキャプション画像グリッド

どんな動きか

グリッドのタイルにマウスを乗せると下から白いキャプション帯がせり上がってラベルが現れ、キーボードフォーカスでも同じく反応します。

ホバーキャプション画像グリッド
新しいタブで開く

09 · ホバーキャプション画像グリッド — 計測 累積面積 10.226% · 動いたフレーム 21/23GODRICH オリジナル · SCSS と React は zip に同梱 · パスワードを見る

:hover + :focus-visibletransform: translateYstagger animation-delay

どこを変えるか

JSは一切使わず、:hover と :focus-visible の二つだけで動きます。

.hg-tile__cap {
  transform: translateY(100%);
  transition: transform .3s ease-out;
}
.hg-tile:hover .hg-tile__cap,
.hg-tile:focus-visible .hg-tile__cap { transform: translateY(0); }

マウス カーソル エフェクトはどこで壊れるか

最初に出会った落とし穴は04番の3Dチルトでした。rotateX・rotateY は見た目だけ傾いてレイアウトの再計算は起こさないはずなのに、320px幅では傾いたカードの見かけの境界ボックスがスクロール領域にそのまま含まれ、画面が横に溢れてしまいました。最初の9度では溢れが残り、6度まで減らしてカード自体も小さくしてから、320pxレンダーでの溢れが消えたことを確認しました。二つ目は05番のドラッグギャラリーです。指でドラッグする動きを transform で再現すると実際のスクロール位置とずれてしまうため、本物のドラッグ操作では必ず scrollLeft を直接動かす必要があります。三つ目は06番の右クリックメニューで、座標をクランプしないと画面の右端・下端でメニューがエリアの外にはみ出してしまいます。

アクセシビリティ — カスタムカーソルが逆に邪魔になるとき

01・03・08番のようにカーソル自体を追いかける要素は、prefers-reduced-motion: reduce で動きが止まるよう transition・animation をすべて切ります。ですが本当の問題は動きよりもカーソル自体です。ロービジョンの方や、トラックパッドの代わりにスイッチ・ジョイスティックのような支援機器を使う方にとって、小さなカスタムカーソルは標準カーソルより見つけにくいことがあります。そのため01番は @media (hover: none), (pointer: coarse) でタッチ端末ではカーソルの点自体を完全に消し、08番は同じ条件で標準カーソルに戻します。02・04番はそもそも隠すべきカーソル要素がなく、ポインタ駆動のトランジションだけが切れます。03番はスポットライトを隠す代わりに文字の色を最初から明るくして内容が常に読めるようにしました。05番のドラッグギャラリーはむしろタッチのほうがよく動きます — ネイティブスクロールがそのまま生きているためです。06・07・09番はマウス操作(右クリック・ドラッグ選択・ホバー)それぞれにキーボード・フォーカスの代替手段(Escape・Tab・:focus-visible)を付け、マウスがなくても同じ機能を使えるようにしました。ポインタ座標を値に変えて画面を描き直す骨組みは、css 背景 パターン ジェネレーターでシード一つから繰り返されます。9つのファイルの圧縮パスワードはc5gmgmdfで、解凍するとvanilla・reactフォルダで同じ9つの動きを並べて比較できます。

FAQ

マウス クリック エフェクトまでCSSだけで作れますか?

一部はそうです。07番の選択色と09番のホバーキャプショングリッドは ::selection と :hover だけで動くCSS専用エフェクトです。残りはカーソル座標を読む必要があるため、pointermove を使う短いバニラJSが必要です。カーソルに合わせて場面全体が傾く演出はマウス 追従 アニメーション cssで角度計算まで解説しています。

カスタムカーソルを使うとサイトが遅くなりますか?

pointermove ごとに transform だけを変えればレイアウトの再計算が発生せず、ほとんどの場合なめらかに動きます。ただしサイズが変わらない要素なら、getBoundingClientRect を毎回測り直さず一度キャッシュしておくとよいです。

これらのエフェクトはモバイルでも動きますか?

ポインタを前提とする01・02・03・04・08番はタッチでは意味がないため、CSSで隠すか別の状態を用意しています。05番のドラッグギャラリーと06・07・09番は、タッチでも自然に動くよう別途対応しています。

出典: MDN Pointer events、MDN prefers-reduced-motion · 関連: 紹介、ホバーインタラクション一覧、LPインタラクション

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

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