「マウスストーカー」は、マウスカーソルに遅れてついてくる図形のことです。サイトのデザインにアイデンティティを持たせるだけでなく、カーソルの見失い防止や、押せるボタンの強調にも使えます。
結論から書くと、マウスカーソルを cursor: none で隠し、代わりの要素をカーソル座標に追従させて作ります。追従を遅らせるとストーカーになります。
この記事では、カーソルの置き換えとマウスストーカーの作り方を、2通りの書き方で解説します。jQuery を使った書き方と、ライブラリなしで transform と requestAnimationFrame を使う書き方です。実装の前に、タッチ端末で無効化する指定を必ず入れてくださいという注意点も最後にまとめます。
スポンサーリンク
マウスカーソルの変更
結論:マウスカーソルは、CSSで作った図形や画像に置き換えられます。cursor: none で本来のカーソルを消し、代わりの要素を座標に追従させます。
今回はCSSで作った図形に置き換えたコードで解説します。
HTML解説
カーソルの代わりになる要素「cursor」を設置します。中に img タグを入れれば、図形ではなく画像をカーソルにできます。
<div id="cursor"></div>
jQuery を使う場合は CDN で読み込みます。後半で紹介するライブラリなしの書き方なら、この読み込みは不要です。
CSS解説
結論:body に cursor: none を指定して本来のカーソルを消し、代わりの要素には pointer-events: none を指定します。
body {
cursor: none;
height: 200px;
}
#cursor {
position: fixed;
top: 0;
left: 0;
width: 40px;
height: 40px;
border-radius: 50%;
background: #333;
/* カーソル要素がクリックを奪わないようにする */
pointer-events: none;
/* 中心をカーソル位置に合わせる */
margin: -20px 0 0 -20px;
}
pointer-events: none は必須です。これを付けないと、カーソル要素が常にマウスの下にあるせいで、リンクもボタンも一切クリックできなくなります。
margin: -20px 0 0 -20px は位置合わせです。top と left に座標を代入すると要素の左上角がカーソル位置に来るため、幅と高さの半分だけ戻して中心を合わせています。
なお、この例では body が空なので height: 200px を指定し、その範囲だけカーソルが変わるようにしています。
jQuery解説
結論:mousemove イベントで e.clientX と e.clientY を取得し、カーソル要素の top と left に代入します。
const cursor = $("#cursor");
$(document).on("mousemove", function (e) {
cursor.css({
top: e.clientY,
left: e.clientX,
});
});
clientX と clientY は、ブラウザの表示領域の左上を原点とした座標です。position: fixed の基準と一致するので、そのまま代入できます。ページをスクロールしても補正は要りません。
遅れてついてくるマウスストーカー
結論:カーソル要素とは別に、もう1つ要素を用意し、そちらの追従を遅らせます。
HTML解説
カーソル要素に加えて、ストーカーとなる要素を設置します。
<div id="cursor"></div>
<div id="stalker"></div>
CSS解説
カーソル要素のCSSは、先ほどのマウスカーソルの変更と同じです。ストーカー要素は、少し大きめの半透明な円にすると追従が分かりやすくなります。
#stalker {
position: fixed;
top: 0;
left: 0;
width: 60px;
height: 60px;
margin: -30px 0 0 -30px;
border-radius: 50%;
background: rgba(51, 51, 51, 0.3);
pointer-events: none;
}
jQuery解説
結論:setTimeout で100ミリ秒だけ遅らせてストーカー要素の位置を更新すると、0.1秒遅れて追従します。
$(document).on("mousemove", function (e) {
cursor.css({ top: e.clientY, left: e.clientX });
setTimeout(function () {
stalker.css({ top: e.clientY, left: e.clientX });
}, 100);
});
この書き方は短く済みますが、マウスを動かした回数だけタイマーが作られるという弱点があります。素早く動かすとタイマーが大量に積み上がり、動きがカクつくことがあります。次に紹介する書き方だと、この問題が起きません。
スポンサーリンク
ライブラリなしで滑らかに追従させる
結論:requestAnimationFrame で毎フレーム位置を少しずつ近づけると、遅れて追従する動きが滑らかになります。jQuery も setTimeout も不要です。
const cursor = document.getElementById("cursor");
const stalker = document.getElementById("stalker");
let mouseX = 0, mouseY = 0; // マウスの現在位置
let stalkerX = 0, stalkerY = 0; // ストーカーの現在位置
document.addEventListener("mousemove", (e) => {
mouseX = e.clientX;
mouseY = e.clientY;
cursor.style.transform = `translate3d(${mouseX}px, ${mouseY}px, 0)`;
});
function loop() {
// 目標との差の10%ずつ近づける(数値を小さくするほど遅れる)
stalkerX += (mouseX - stalkerX) * 0.1;
stalkerY += (mouseY - stalkerY) * 0.1;
stalker.style.transform = `translate3d(${stalkerX}px, ${stalkerY}px, 0)`;
requestAnimationFrame(loop);
}
loop();
CSS側は top / left ではなく transform を使うので、位置合わせも transform に含めます。
#cursor,
#stalker {
position: fixed;
top: 0;
left: 0;
pointer-events: none;
/* 中心合わせは transform 側でまとめる */
margin: 0;
translate: -50% -50%;
}
▼2つの書き方の違い
| jQuery + setTimeout | requestAnimationFrame + transform | |
|---|---|---|
| ライブラリ | jQuery が必要 | 不要 |
| 位置の指定 | top / left |
transform |
| 描画の負荷 | 毎回レイアウトが再計算される | 合成のみで済むため軽い |
| 速く動かしたとき | タイマーが積み上がりカクつく | フレームに同期して滑らか |
| 遅れ具合の調整 | ミリ秒で指定 | 係数(0.1など)で指定 |
top と left を書き換えるとブラウザはレイアウトの再計算からやり直しますが、transform は合成処理だけで済みます。毎フレーム動かす用途では、この差が体感に出ます。
実装するときの必須の注意点
結論:タッチ端末では必ず無効化してください。cursor: none だけが効いて、代わりのカーソルが出ない状態になります。
スマートフォンやタブレットには mousemove がありません。そのためカーソル要素は初期位置から動かず、それでいて cursor: none は適用されます。マウスを接続したときに何も表示されない、という状態になります。
対処は、マウス操作ができる環境に限定するメディアクエリで囲むことです。
/* マウスなど正確なポインタがあり、ホバーできる環境だけに適用する */
@media (hover: hover) and (pointer: fine) {
body {
cursor: none;
}
#cursor,
#stalker {
display: block;
}
}
#cursor,
#stalker {
display: none;
}
あわせて、動きを減らす設定にしているユーザーへの配慮も入れておきます。OSで「視差効果を減らす」を有効にしている人には、追従アニメーションが不快に感じられることがあります。
@media (prefers-reduced-motion: reduce) {
body {
cursor: auto;
}
#cursor,
#stalker {
display: none;
}
}
この2つを入れておかないと、環境によってはカーソルが完全に見えなくなり、サイトが操作不能になります。装飾のための実装なので、成立しない環境では素直に標準のカーソルへ戻すのが安全です。
応用:重なった部分の色を反転させる
結論:mix-blend-mode: difference を指定すると、カーソルが重なった部分の色が反転します。白背景では黒く、黒背景では白く見えるので、どんな背景でもカーソルを見失いません。
#cursor {
background: #fff;
mix-blend-mode: difference;
}
文字の上に重なったときに文字色が反転するので、読みやすさを保ったまま演出を足せます。背景色が場所によって変わるサイトで特に効きます。
ホバーしたときにカーソルを大きくする、といった変化を付ける場合は transition を併用します。transform は毎フレーム書き換えているので、width や scale のほうに指定してください。
スクロール量に応じて色を変える演出と組み合わせる場合は、CSSやJavaScriptでスクロールに応じて色を変える方法もあわせてどうぞ。
こうしたインタラクションをまとめて学びたい場合は、リペリングエフェクトの仕組みと実装例でもマウス座標を使った実装を扱っています。基礎から固めたい場合はWebコーディングにオススメの学習本をどうぞ。