GODRICH

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

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

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

以下の9つは人気順ではなく、ポインタが画面の上で実際に出会う順番で並んでいます。じっとしていて、リンクの上で形が変わり(01)、ボタンに近づくと引き寄せられ(02)、カードの上を通ると光が追いかけて傾き(03・04)、画面を手で引っぱり(05)、右クリックでメニューが出て(06)、文章をなぞると色が付き(07)、ページを読み進めるとリングが満ちていき(08)、最後にグリッドを一巡するとキャプションが現れます(09)。

01カーソル形状モーフ

リンクにカーソルを乗せると小さな点が矢印入りの青い円に広がり、離れると点に戻ります。pointermove で点の位置を動かし、pointerenterpointerleave で形を切り替えます。

pointermove translatedata-cursor 속성pointer:coarse 터치 제외
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マグネティックプルボタン

ボタン周辺にカーソルを近づけるとその方向へわずかに引き寄せられ、離れると元の位置に戻ります。ヒット領域の中心からの距離に係数(0.4)を掛けて translate の値を作ります。

pointermove 거리×계수transform: translatepointerleave 리셋
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スポットライトカードリビール

カード上でカーソルを動かすと丸い光がついてきて、光が通った場所の薄い文字が明るく浮かび上がります。mix-blend-mode: soft-light で光を背景に混ぜ、screen で文字を明るくします。

mix-blend-mode: soft-lightpointermove translatehover:none 대체
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チルトカード

カード上のカーソル位置に応じてカードが立体的に傾き、斜めの光沢も一緒に流れます。カーソル座標を0〜1の比率に変換し、rotateXrotateY の角度を計算します。

perspective + rotateX/rotateYbackground-position 스윕신뢰감 있는 밝은 톤
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ドラッグスクロールギャラリー

タイルを手でつかんで引くようにドラッグすると横にスクロールし、離した位置で止まります。transform ではなく本物の scrollLeft を動かすので、タッチでもネイティブスクロールがそのまま続きます。

scrollLeft 직접 조작overflow-x: auto 네이티브 터치cursor: grab/grabbing
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カスタム右クリックメニュー

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

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テキスト選択のマーカー色

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

::selection 색 지정selectionchange 이벤트getBoundingClientRect 배지 위치
::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スクロール進捗カーソルリング

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

stroke-dashoffsetscrollTop/scrollHeight 비율pointermove translate
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ホバーキャプション画像グリッド

グリッドのタイルにマウスを乗せると下から白いキャプション帯がせり上がってラベルが現れ、キーボードフォーカスでも同じく反応します。JSは一切使わず、:hover:focus-visible の二つだけで動きます。

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

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

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

FAQ

マウス カーソル エフェクトはCSSだけで作れますか?

一部はそうです。07番の選択色と09番のホバーキャプショングリッドは ::selection:hover だけで動くCSS専用エフェクトです。残りはカーソル座標を読む必要があるため、pointermove を使う短いバニラJSが必要です。

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

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

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

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

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

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

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