1. HOME
  2. HTML CSS
  3. JavaScript カスタムマウスカーソルを実装する方法
HTML CSS - 2025-08-08

JavaScript カスタムマウスカーソルを実装する方法

ウェブサイトに独自のスタイルを取り入れるカスタムカーソルは、ユーザー体験を向上させる効果的な手法の一つです。
今回は案件で実装したマウスカーソルを記事にまとめてみました。
※JavaScriptコードは、クラス構文とES Modules形式で書いています。

1. HTML:カーソル要素の準備

まず、カスタムカーソルとして表示する要素をHTMLに用意します。ここでは、シンプルにドットとアウトラインの2つのdiv要素を使用します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Custom Cursor Guide</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>

    <div class="js-cursor-dot"></div>
    <div class="js-cursor-dot-outline"></div>

    <h1>カスタムカーソルデモ</h1>
    <p><a href="#">リンク1</a></p>
    <p class="js-link">カスタムリンク要素</p>
    <p><a href="#">リンク2</a></p>
    
    <script type="module" src="main.js"></script>

</body>
</html>

このHTMLでは、**js-cursor-dotjs-cursor-dot-outline**というクラス名を持つdiv要素を作成しました。JavaScriptからこれらの要素にアクセスするため、特定のクラス名を付与しておくと便利です。

また、<script>タグに**type="module"**を追加し、ES Modules形式でJavaScriptファイルを読み込んでいます。

2. CSS:カーソルの見た目と動作設定

次に、HTMLで作成した要素にスタイルを適用します。ここでは、カーソルの見た目だけでなく、重要な動作設定もCSSで行います。

/* デフォルトのカーソルを非表示にする */
body {
    cursor: none;
    height: 200vh; /* スクロールテスト用 */
}

/* カスタムカーソル要素の共通設定 */
.js-cursor-dot, 
.js-cursor-dot-outline {
    /* 常にビューポートに固定する */
    position: fixed;
    top: 0;
    left: 0;
    /* transformの起点と中央揃え */
    transform: translate(-50%, -50%);
    border-radius: 50%;
    /* カーソル下の要素のクリックを妨げない */
    pointer-events: none;
    /* 最前面に表示 */
    z-index: 9999;
    /* サイズと不透明度の変化をアニメーションさせる */
    transition: transform 0.2s ease-out, opacity 0.2s ease-out;
}

/* ドットのスタイル */
.js-cursor-dot {
    width: 8px;
    height: 8px;
    background-color: #333;
}

/* アウトラインのスタイル */
.js-cursor-dot-outline {
    width: 40px;
    height: 40px;
    border: 2px solid #333;
}

/* インタラクティブ要素(リンクなど)にホバーした時のデフォルトカーソルも非表示にする */
a, .js-link {
    cursor: none;
    color: blue;
    text-decoration: underline;
    margin: 20px;
    display: inline-block;
}

このCSSで最も重要な設定は以下の3つです。

  1. body { cursor: none; }: これにより、ブラウザのデフォルトカーソルを非表示にし、カスタムカーソルだけが表示されるようになります。
  2. position: fixed;: カーソル要素がスクロールしても画面上の同じ位置に留まるように設定します。JavaScriptと連携して、スクロールに追従する動きを実現するために不可欠です。
  3. pointer-events: none;: この設定により、カーソル要素自体がマウスイベントをブロックしなくなります。カーソルの下にあるリンクやボタンなどの要素が、正常にクリックできるようになります。

3. JavaScript:カーソルの動きとインタラクション

最後に、カーソルをマウスの動きに追従させたり、ホバー時に見た目を変えたりするロジックをJavaScriptで実装します。

Cursor.js

/**
 * Cursor.js
 *
 * カスタムカーソルを実装し、インタラクティブな要素(リンクなど)にホバーした際に
 * カーソルの見た目を変更する機能を提供します。
 */
class Cursor {
  constructor() {
    this.delay = 2;
    this._x = 0;
    this._y = 0;
    this.endX = window.innerWidth / 2;
    this.endY = window.innerHeight / 2;
    this.cursorVisible = true;
    this.cursorEnlarged = false;
    this.$dot = null;
    this.$outline = null;

    this.animateDotOutline = this.animateDotOutline.bind(this);
  }

  /**
   * カーソルモジュールの初期化
   */
  init() {
    this.$dot = document.querySelector('.js-cursor-dot');
    this.$outline = document.querySelector('.js-cursor-dot-outline');

    if (!this.$dot || !this.$outline) {
      console.warn('カーソル要素が見つかりません。HTMLを確認してください。');
      return;
    }

    this.dotSize = this.$dot.offsetWidth;
    this.outlineSize = this.$outline.offsetWidth;

    this.setupEventListeners();
    this.animateDotOutline();
  }

  /**
   * イベントリスナーの設定
   */
  setupEventListeners() {
    const interactiveElements = document.querySelectorAll('a, .js-link');

    interactiveElements.forEach(el => {
      el.addEventListener('mouseover', () => this.toggleCursorSize(true));
      el.addEventListener('mouseout', () => this.toggleCursorSize(false));
    });

    document.addEventListener('mousedown', () => this.toggleCursorSize(true));
    document.addEventListener('mouseup', () => this.toggleCursorSize(false));

    document.addEventListener('mousemove', e => {
      this.cursorVisible = true;
      // ビューポート座標を使用することでスクロールに追従
      this.endX = e.clientX;
      this.endY = e.clientY;
      this.$dot.style.top = `${this.endY}px`;
      this.$dot.style.left = `${this.endX}px`;
      this.toggleCursorVisibility();
    });

    document.addEventListener('mouseenter', () => this.toggleCursorVisibility(true));
    document.addEventListener('mouseleave', () => this.toggleCursorVisibility(false));
  }

  /**
   * ドットアウトラインのアニメーション
   */
  animateDotOutline() {
    this._x += (this.endX - this._x) / this.delay;
    this._y += (this.endY - this._y) / this.delay;

    this.$outline.style.top = `${this._y}px`;
    this.$outline.style.left = `${this._x}px`;

    requestAnimationFrame(this.animateDotOutline);
  }

  /**
   * カーソルサイズの切り替え
   */
  toggleCursorSize(enlarged) {
    const scaleDot = enlarged ? 0.75 : 1;
    const scaleOutline = enlarged ? 1.5 : 1;
    this.$dot.style.transform = `translate(-50%, -50%) scale(${scaleDot})`;
    this.$outline.style.transform = `translate(-50%, -50%) scale(${scaleOutline})`;
  }

  /**
   * カーソル表示の切り替え
   */
  toggleCursorVisibility(visible) {
    const opacity = visible ? 1 : 0;
    this.$dot.style.opacity = opacity;
    this.$outline.style.opacity = opacity;
  }
}

/**
 * カスタムカーソルを開始
 */
export function cursorStart() {
  const customCursor = new Cursor();
  customCursor.init();
}

main.js

import { cursorStart } from './Cursor.js';

document.addEventListener('DOMContentLoaded', () => {
  cursorStart();
});

特に重要なポイントは、mousemoveイベントで**e.clientXe.clientY**というビューポート座標を使用している点です。これにより、CSSのposition: fixedと連携して、スクロール中もマウスに正確に追従する動きを実現しています。

animateDotOutlineメソッドではrequestAnimationFrameと慣性の計算を組み合わせることで、アウトラインがドットに少し遅れて追従する滑らかなアニメーションを生み出しています。

サンプル