カスタムカーソルとは

OS 標準のマウスカーソルを非表示にし、代わりに丸い図形などをマウスの動きに追従させて表示する演出。

概要

cursor: none で OS 標準のマウスカーソルを消し、代わりに丸などの図形(マウスストーカーと呼ばれます)を JavaScript でマウスの座標に追従させて表示する演出です。 リンクやボタンの上に乗せたときにストーカーを拡大させるなど、通常のカーソルではできない演出ができます。 マウス操作を前提にした演出のため、指で操作するタッチ端末では使わない配慮が必要です。

別名

表記ゆれ:カスタムカーソル、マウスストーカー、カーソル追従

AI への伝え方

「マウスカーソルを丸い図形に置き換えて、リンクに乗せたら拡大するようにして」で伝わります。 タッチ端末ではマウスがないため、「タッチ端末では無効にして」と伝えて pointer: fine のメディアクエリで出し分けてもらうと安全です。

似たパーツとの違い

「ホバーエフェクト」は要素そのものがホバーで変化する演出全般を指しますが、カスタムカーソルはカーソル表示自体を独自の図形に置き換える点が異なり、両者を組み合わせて使うこともよくあります。

デモ

マウスを枠内で動かしてください(タッチ端末では無効になります)。

ホバーで拡大するリンク

コード

HTML
<div class="cursor-demo">
  <p>マウスを枠内で動かしてください(タッチ端末では無効になります)。</p>
  <a class="cursor-demo__link" href="#" onclick="return false;">ホバーで拡大するリンク</a>
  <div class="cursor-demo__dot" aria-hidden="true"></div>
</div>
CSS
.cursor-demo {
  position: relative;
  min-height: 160px;
  padding: 24px;
  background: #f2f2f2;
  border-radius: 8px;
  font-family: sans-serif;
  color: #333333;
  overflow: hidden;
}

.cursor-demo__link {
  display: inline-block;
  margin-top: 12px;
  color: #0017c1;
}

.cursor-demo__dot {
  position: absolute;
  top: 0;
  left: 0;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #0017c1;
  opacity: 0.6;
  pointer-events: none;
  transform: translate(-50%, -50%);
  transition: width 0.15s ease, height 0.15s ease, opacity 0.15s ease;
  /* 初期状態は枠外扱いで非表示にしておく */
  display: none;
}

.cursor-demo__dot.is-active {
  display: block;
}

.cursor-demo__dot.is-hover {
  width: 40px;
  height: 40px;
}

/* マウス操作が可能な環境(pointer: fine)だけカスタムカーソルを有効にする */
@media (pointer: fine) {
  .cursor-demo {
    cursor: none;
  }

  .cursor-demo__link {
    cursor: none;
  }
}
JS
const stage = document.querySelector(".cursor-demo");
const dot = document.querySelector(".cursor-demo__dot");
const link = document.querySelector(".cursor-demo__link");

// タッチ端末など、精密なポインターがない環境では追従を有効にしない
const hasFinePointer = window.matchMedia("(pointer: fine)").matches;

if (stage && dot && hasFinePointer) {
  stage.addEventListener("mousemove", (event) => {
    const rect = stage.getBoundingClientRect();
    dot.style.left = `${event.clientX - rect.left}px`;
    dot.style.top = `${event.clientY - rect.top}px`;
    dot.classList.add("is-active");
  });

  // 枠の外に出たら非表示にする
  stage.addEventListener("mouseleave", () => {
    dot.classList.remove("is-active");
  });

  link?.addEventListener("mouseenter", () => dot.classList.add("is-hover"));
  link?.addEventListener("mouseleave", () => dot.classList.remove("is-hover"));
}