マウス カーソル エフェクト 9選 — コピペOK
マウス カーソル エフェクトとは、カーソルの移動やクリック・ドラッグ・スクロールに合わせて画面が反応する小さなインタラクションのことです。カーソルを動かしただけなのに、ボタンが寄ってきたりカードが立体的に傾いたりするものもここに含まれます。
自動再生 · タイルを押すとその項目へ · すべて1つのzip
- 01 カーソル形状モーフ
- 02 マグネティックプルボタン
- 03 スポットライトカードリビール
- 04 3Dチルトカード
- 05 ドラッグスクロールギャラリー
- 06 カスタム右クリックメニュー
- 07 テキスト選択のマーカー色
- 08 スクロール進捗カーソルリング
- 09 ホバーキャプション画像グリッド
以下の9つは人気順ではなく、ポインタが画面の上で実際に出会う順番で並んでいます。じっとしていて、リンクの上で形が変わり(01)、ボタンに近づくと引き寄せられ(02)、カードの上を通ると光が追いかけて傾き(03・04)、画面を手で引っぱり(05)、右クリックでメニューが出て(06)、文章をなぞると色が付き(07)、ページを読み進めるとリングが満ちていき(08)、最後にグリッドを一巡するとキャプションが現れます(09)。
01カーソル形状モーフ
リンクにカーソルを乗せると小さな点が矢印入りの青い円に広がり、離れると点に戻ります。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マグネティックプルボタン
ボタン周辺にカーソルを近づけるとその方向へわずかに引き寄せられ、離れると元の位置に戻ります。ヒット領域の中心からの距離に係数(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スポットライトカードリビール
カード上でカーソルを動かすと丸い光がついてきて、光が通った場所の薄い文字が明るく浮かび上がります。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チルトカード
カード上のカーソル位置に応じてカードが立体的に傾き、斜めの光沢も一緒に流れます。カーソル座標を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ドラッグスクロールギャラリー
タイルを手でつかんで引くようにドラッグすると横にスクロールし、離した位置で止まります。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カスタム右クリックメニュー
エリアを右クリックするとブラウザ標準メニューの代わりに、コピー・編集・削除アイコンメニューがクリック位置に弾むように現れます。座標はエリアの外に出ないようクランプしています。
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 {
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 をスクロール比率だけ減らします。
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 の二つだけで動きます。
.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)を付け、マウスがなくても同じ機能を使えるようにしました。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 events、MDN prefers-reduced-motion · 関連: 紹介、ホバーインタラクション一覧、LPインタラクション