【Chrome 拡張開発入門】会社 HP のカーソルエフェクトをシンプルにしてみた 🪄

## はじめに

みなさん、弊社のホームページをご覧になったことはありますか？

https://www.skywill.jp/

かっこいいです 🌟

ただ、カーソルのパーティクルが人魂みたいで、寝る前に暗い部屋で見るとちょっと怖いです 👻

![人魂.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3848132/cb249c5a-a97a-4142-9abc-db36b5489715.png)

「じゃあ、見るのやめておこうかな...」と思ったあなた！ちょっと待って！✋

**「ちょっと怖がりなあなたにお届けする弊社 HP」** ということで、カーソルのパーティクルを無効化する Chrome 拡張を真心込めて作りました 🔨（ただ Chrome 拡張を作りたかっただけです）

Edge でも動きます。

## 成果物 🏁

https://github.com/kamata-bug-factory/swhp-chrome-extension

今回作った Chrome 拡張は必要最小限な構成です。

↓ ディレクトリ構成

```
swhp-chrome-extension/
├── disable-particle.js
└── manifest.json
```

↓ 設定ファイル

```json:manifest.json
{
  "manifest_version": 3,
  "name": "swhp-chrome-extension",
  "version": "1.0",
  "description": "SW社ホームページでマウスカーソルに付与されたパーティクルを無効にする",
  "permissions": ["activeTab"],
  "content_scripts": [
    {
      "matches": ["https://www.skywill.jp/*"],
      "js": ["disable-particle.js"],
      "run_at": "document_start"
    }
  ]
}
```

↓ 実際に動かすプログラム（エントリーポイント）

```js:disable-particle.js
document.addEventListener('DOMContentLoaded', () => {
  // ParticleJSは描画するためのコンテナをmyCanvasに追加している
  // myCanvasをDOMツリーから削除すればパーティクルを無効化できる
  const myCanvas = document.getElementById('myCanvas');
  if (myCanvas) {
    myCanvas.remove();
  }

  // パーティクルのために非表示にされていたカーソルを表示する
  document.body.style.cursor = 'default';

  const links = document.querySelectorAll('a');
  const pointer = document.getElementById('POINTER');

  // aタグにmouseenterしたらPOINTERを表示、mouseleaveしたら非表示にする
  // styleの各属性はオリジナルと同じものを設定する
  links.forEach((link) => {
    link.addEventListener('mouseenter', () => {
      pointer.style.visibility = 'inherit';
      pointer.style.opacity = '1';
      pointer.style.transform = 'translate(-50%, -50%) scale(1.5, 1.5)';
    });

    link.addEventListener('mouseleave', () => {
      pointer.style.visibility = 'hidden';
      pointer.style.opacity = '0';
      pointer.style.transform = 'translate(-50%, -50%) scale(1, 1)';
    });
  });

  let mouseX = 0;
  let mouseY = 0;

  // マウスカーソルの動きに合わせてPOINTERを追従する
  window.addEventListener('mousemove', (event) => {
    mouseX = event.clientX;
    mouseY = event.clientY;

    // POINTERが表示されているとき、マウスカーソルの位置に追従させる
    if (pointer.style.visibility === 'inherit') {
      pointer.style.left = `${mouseX}px`;
      pointer.style.top = `${mouseY + window.scrollY}px`;
    }
  });

  // スクロール時にもPOINTERを追従する
  window.addEventListener('scroll', () => {
    if (pointer.style.visibility === 'inherit') {
      pointer.style.top = `${mouseY + window.scrollY}px`;
    }
  });
});
```

## 拡張機能の読み込み方 🧑‍🏫

拡張機能を作ったら...

デベロッパーモードを ON にして、

![デベロッパーモード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3848132/d58618f5-c4a0-4656-a877-c41a9cc71167.png)

ここのボタンをポチッと押すと、Mac なら Finder、Windows ならエクスプローラーが立ち上がって自作の拡張機能を読み込めます。

![読み込む.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3848132/09546ea0-a363-49b6-9131-309e57d40675.png)

インポートするのは、以下の `swhp-chrome-extension` フォルダです。

↓ ディレクトリ構成（再掲）

```
swhp-chrome-extension/
├── disable-particle.js
└── manifest.json
```

開発を進めて再読み込みしたいときは、ここのリロードボタンを押せば反映してくれます。
対象ページ（弊社 HP）のリロードも忘れずに！

![更新する.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3848132/b2f6de42-609f-4c63-acda-6fb0ecf811b1.png)

## ポイント 💡

この拡張機能でやっていることは大きく以下の 2 つです。

1. マウスカーソルのパーティクルを無効化する
2. リンクをホバーした時のポインタアニメーションを復活させる

### 1. マウスカーソルのパーティクルを無効化する 🙅

開発者ツールの Sources タブを探検してみたら、パーティクルには [ParticleJS](https://github.com/ics-creative/ParticleJS) というライブラリが使われていることがわかりました。

![スクリーンショット 2025-04-01 23.08.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3848132/e3ef8067-419c-4497-b316-79d807666fba.png)

ParticleJS では `canvas` 要素を用意し、そこにパーティクルを描画するためのコンテナを追加しています。([公式の実装例](https://ics-creative.github.io/ParticleJS/docs/examples/mouse-tracking.html))
そして、内部的に `window` にイベントリスナーを登録しているようです。

実際、`canvas` くんがいました！

![canvas.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3848132/4acbc622-a785-4c06-b793-82da4b8af99f.png)

本来であれば、`mousemove` イベントにバインドされたリスナーを取得して `removeEventListener` したいところですが、拡張機能側からイベントリスナーにアクセスするのは難しいです 😵‍💫
それならいっそ `canvas` を消しちゃえ！💥

```js:disable-particle.js
// ParticleJSは描画するためのコンテナをmyCanvasに追加している
// myCanvasをDOMツリーから削除すればパーティクルを無効化できる
const myCanvas = document.getElementById('myCanvas');
if (myCanvas) {
  myCanvas.remove();
}
```

これでパーティクルがいなくなります。ゴーストバスターズ！

いなくなったパーティクルの代わりに、マウスカーソルを復活させてあげましょう。

```js:disable-particle.js
// パーティクルのために非表示にされていたカーソルを表示する
document.body.style.cursor = 'default';
```

### 2. リンクをホバーしたときのポインタアニメーションを復活させる 🏋️

無事 ParticleJS の活躍の舞台（`canvas`）を破壊したわけですが、同時にポインタアニメーションも壊れます 🤯

- リンクにホバーしたときのポインタアニメーションが効かなくなる
- ポインタがカーソルの動きに追従しなくなる

仕方がないので、これらの処理を自分で書きます ✍️

#### リンクにホバーしたときのポインタアニメーション

開発者ツールの `Elements` タブを観察すると、`mouseenter` したときと `mouseleave` したときで、`visibility`, `opacity`, `transform` の値が変わるとわかります。

↓ `mouseenter` したとき

![ポインタアニメーションスタイル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3848132/5e6687c7-1e7b-4ba5-8443-0a51c5ec6253.png)

↓ `mouseleave` したとき

![パーティクルスタイル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3848132/0472dc63-2da7-4f72-9f5e-10f7410d874b.png)

それぞれのイベントが配信されたとき、3 つの属性に適切な値を設定してあげることでアニメーションが復活します 💪

```js:disable-particle.js
const links = document.querySelectorAll('a');
const pointer = document.getElementById('POINTER');

// aタグにmouseenterしたらPOINTERを表示、mouseleaveしたら非表示にする
// styleの各属性はオリジナルと同じものを設定する
links.forEach((link) => {
  link.addEventListener('mouseenter', () => {
    pointer.style.visibility = 'inherit';
    pointer.style.opacity = '1';
    pointer.style.transform = 'translate(-50%, -50%) scale(1.5, 1.5)';
  });

  link.addEventListener('mouseleave', () => {
    pointer.style.visibility = 'hidden';
    pointer.style.opacity = '0';
    pointer.style.transform = 'translate(-50%, -50%) scale(1, 1)';
  });
});
```

#### ポインタをカーソルの動きに追従させる

`mousemove` イベント、`scroll` イベントが配信されたとき、ポインタが表示されていたら、マウスカーソルの座標に移動させます。

```js:disable-particle.js
let mouseX = 0;
let mouseY = 0;

// マウスカーソルの動きに合わせてPOINTERを追従する
window.addEventListener('mousemove', (event) => {
  mouseX = event.clientX;
  mouseY = event.clientY;

  // POINTERが表示されているとき、マウスカーソルの位置に追従させる
  if (pointer.style.visibility === 'inherit') {
    pointer.style.left = `${mouseX}px`;
    pointer.style.top = `${mouseY + window.scrollY}px`;
  }
});

// スクロール時にもPOINTERを追従する
window.addEventListener('scroll', () => {
  if (pointer.style.visibility === 'inherit') {
    pointer.style.top = `${mouseY + window.scrollY}px`;
  }
});
```

## 動作確認

パーティクルが消えて、マウスカーソルが表示されているし、

![マウスカーソル.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3848132/8665faee-2886-4942-811d-33244d73ba95.png)

リンクにホバーすると、ポインタが表示される！

![ポインタアニメーション.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3848132/c610378c-c838-4643-af42-1f8098ddbae7.png)

いい感じ！！！！！

## おわりに

Chrome 拡張は、サクッと作れるのがよいですね！
「これ、もうちょっとこうだったらいいのに！」と思ったとき、気軽に作ってみるのもアリかも 🤔

楽しい拡張機能ライフを！🚀

## 参考

- Chrome 拡張の作り方 (超概要)
  https://qiita.com/RyBB/items/32b2a7b879f21b3edefc