【Javascript】カーソルの周りが光るSpotlightを作ってみた

# 初めに
暗い背景にスポットライトを当てたような効果をcssやjavascriptを使って実装したいと思い、学習した内容や参考サイトをもとに作ってみました。

※内容に間違いなどがある場合はご指摘をよろしくお願いします。

# やりたいこと
カーソルの動きに沿ってスポットライトを当て、暗くて読みづらい文字列が読めるようにしたい。

# 完成形のイメージ
実際に出来上がったコードと結果は以下の通りです。

<p class="codepen" data-height="265" data-theme-id="light" data-default-tab="html,result" data-user="redrabbit1104" data-slug-hash="rNLvQMB" style="height: 265px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="rNLvQMB">
  <span>See the Pen <a href="https://codepen.io/redrabbit1104/pen/rNLvQMB">
  rNLvQMB</a> by redrabbit1104 (<a href="https://codepen.io/redrabbit1104">@redrabbit1104</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

# Point
1. 背景は常に暗い状態にしなければならない
2. 背景には文字列が書かれている
3. マウスのカーソルの動きに沿ってスポットライトの効果が出るようにする
4. スポットライトは丸い形をしている
5. スポットライトの真ん中にカーソルが位置する
6. カーソルの当たった部分以外は暗い状態のままにする

# まずはhtmlとcssを用意する
背景になる部分をspot、そして文字列を含むspot__containerを用意します。

```html
<body>
  <div class="spot">
    <div class="spot__container">
      <p> Lorem ipsum dolor sit amet consectetur adipisicing elit. Odit, reiciendis ullam sit libero recusandae
        //中略
        magni possimus illum amet optio aut eum ea sapiente similique ad iure dolorum ut, quidem cum architecto.
      </p>
    </div>

    <script src="spot_light.js"></script>
</body>
```
css(実際にはscssを使用）で背景の位置とサイズを決めます。画面全体にしたいのでwidthとheightを100%にします。spot__containerには文字列が入りますが、widthを70vwに指定、上下の余白を20px、真ん中に来るように左右の余白はautoにします。

```scss
.spot {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  &__container {
    width: 70vw;
    margin: 20px auto;
  }
}
```

#課題①-スポットライトの実装
スポットライトは周りが暗く、光が当たった部分だけ明るくなります。そのような効果をどのように実装すればでしょうか。スポットライトは球の中心点から一定の距離までは明るさを保ち、遠くなると徐々に暗くなります。これは<b>cssのgradientを使えば同じような効果を得られます</b>。その中でも円形にしたいので、<b>radial-gradient</b>を試してみました。

```html
//radial-gradientの構文
radial-gradient(形状とサイズ at 中心位置, 開始色, 途中色, 終了色);
```
まず、htmlの文字列が邪魔なのでコメントアウトします。

```html
<body>
  <div class="spot">
    <div class="spot__container">
      <!-- <p> Lorem ipsum dolor sit amet consectetur adipisicing elit. Odit, reiciendis ullam sit libero recusandae
        //中略
        magni possimus illum amet optio aut eum ea sapiente similique ad iure dolorum ut, quidem cum architecto. -->
      </p>
    </div>
```
そしてspotにradial-gradientを以下のように記述します。

```sass
.spot {
  //中略
  background-image: radial-gradient(   //追記
    circle at 50% 50%,
    blue 20px,
    yellow 50px,
    green 150px
  );
  &__container {
　//中略
  }
}
```
表示される結果を見てみると、画面の真ん中に円の形をしたgradientが表示されました。
![スクリーンショット 2021-05-22 15.48.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/966135/d6e315e1-b7d8-3b53-1f68-55693c5d3a40.png)
circle at 50% 50%によって円の中心となる位置を画面の真ん中にすることができました。つまり50% 50%が円の座標になります。blueは円の中心点から20pxまで続きますが徐々に色が薄くなります。yellowは50pxから始まり、greenは150pxから色が変わります。

### 円の外側を薄暗くすればいいのでは？
radial-gradientの一番外側の色をgreenではなく薄暗い黒にすれば、周りが暗いイメージになると思いました。実際にどうなるか見てみたいので、文字列もコメントアウトした部分を取り消し元に戻してみます。

```scss
.spot {
  //中略
  background-image: radial-gradient(
    circle at 50% 50%,
    blue 20px,
    yellow 50px,
    rgba(0, 0, 0, 0.9) 50px  //薄暗い色にする
  );
  &__container {
　 //中略
  }
}
```
![スクリーンショット 2021-05-22 15.59.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/966135/497dfc36-a0a4-57c8-7026-09e5086ce4b5.png)
円の周りの文字列が暗い状態ですが見えます。
この状態で途中色, 終了色をスポットライトみたいな感じに仕上げればいいなと思い、それに近い色を探したらtransparentに辿り着きました。透明にすれば背景の文字列も見えるはず。

```sass
.spot {
  //中略
  background-image: radial-gradient(
    circle at 50% 50%,
    transparent 20px,          //色をtransparentに修正
    transparent 50px,　　　　　 //色をtransparentに修正
    rgba(0, 0, 0, 0.9) 50px
  );
  &__container {
  //中略
  }
}
```
近づいてきました。
![スクリーンショット 2021-05-22 16.06.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/966135/239aa912-1e81-9406-25a4-373d093de069.png)
暗いところとtransparentのスポットライトの境界線がはっきりし過ぎて不自然なので、黒色の部分のスタート位置を50pxから100pxにしてみました。

```scss
.spot {
  //中略
  background-image: radial-gradient(
    circle at 50% 50%,
    transparent 20px,
    transparent 50px,
    rgba(0, 0, 0, 0.9) 100px     //50px -> 100pxに修正
  );
  &__container {
  //中略
  }
}
```
なかなかいい感じに仕上がりました。これでスポットライトが実現できました。
![スクリーンショット 2021-05-22 16.09.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/966135/ec6050cc-8963-539a-f0dc-b03815e2b961.png)

#課題②-マウスのカーソルに沿ってスポットライトが動くようにする
マウスが動いた形跡に合わせ、カーソルのところだけスポットライトを当てたいので、javascriptで実現できると思いました。
### 考え方-Point
①radial-gradientのcircle at 50% 50%のところで50% 50%というのはgradientが始まる円のx,y座標
②このx、y座標にマウスのカーソルの位置情報（座標）を代入すれば、カーソルがgradientの円の中心点になり、スポットライトの当たる部分になる
③マウスが動いたタイミングで、リアルタイムに②のマウスのカーソルの位置情報を取得してradial-gradientのcircle at x座標 y座標に代入することができれば、カーソルが当たっている部分がスポットライトになる

### カーソルが動いた時のカーソルのx,y座標の値を取得する
javascriptでカーソルが動いた時の位置情報はMouseEvent.clientX、MouseEvent.clientYで取得できます。

https://developer.mozilla.org/ja/docs/Web/API/MouseEvent/clientX

ここでMouseEventはaddEventlistenerでmousemoveを指定した際に発生するイベントであることがわかりました。スポットライトの対象になるspot要素をquerySelectorで取得し、spotLightという関数を定義します。この関数の引数をevent(eでもいいしなんでもいいです）にし、このeventの中身をみたいのでconsole.log(event)で表示してみました。また、addEventListenerで第一引数をmousemoveにし、第二引数を先ほど定義したspotLightという関数にしました。これでマウスのカーソルが動くとMoveEventが発生し、そのeventの中身が見れるはずです。

```javascript
"use strict";
const spot = document.querySelector(".spot");
const spotLight = (event) => {
  console.log(event);
};

document.addEventListener("mousemove", spotLight);
```
コンソールで確認してみたら、MouseEventがいっぱい表示されました。マウスが動く度にコンソールにMouseEventが表示されるからです。
![スクリーンショット 2021-05-22 16.30.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/966135/76194e91-1e9b-0138-3931-58b17bdf4add.png)
MouseEventをクリックしたら中身が見れました。オブジェクトの形になっていて中にはclientXとclientYというプロパティがあります。これがマウスのx,y座標です。
![スクリーンショット 2021-05-22 16.30.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/966135/d279d7a6-2379-9edd-1010-2b7d6d5a7e30.png)

###javascriptでcssのstyleを操作する
cssのradial-gradientのcircle at 50% 50%の50% 50%のところは円の中心点の座標x,yでした。<b>このx,yをjavascriptのmouseイベントで取得したx,y座標を代入すれば、マウスのカーソルの座標とradial-gradientの円の座標を一致させることができます。</b>
この考え方をベースにしてsetAttributeを使って関数spotLightに追記します。

```javascript
"use strict";
const spot = document.querySelector(".spot");
const spotLight = (event) => {
  spot.setAttribute(
    "style",
    `background-image:
     radial-gradient(circle at ${event.clientX}px ${event.clientY}px, 
     transparent  0px, transparent 30px, rgba(0,0,0,0.9) 50px)`
  );
};

document.addEventListener("mousemove", spotLight);
```
console.log(event)は不要なので削除します。また、spotにsetAttributeでstyle属性を追加します。そしてbackground-imageにradial-gradientを記述します。x,y座標はxのところにevent.clientXを、yのところにはevent.clientYをそれぞれ代入します。これはeventのプロパティで確認した値です。
また、cssで記述したradial-gradientはもう必要ないので（javascriptで記述済み）削除します。

```scss
.spot {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  //削除する
  background-image: radial-gradient(   
    circle at 50% 50%,
    transparent 20px,
    transparent 50px,
    rgba(0, 0, 0, 0.9) 100px
  );
  //ここまで
  &__container {
    width: 70vw;
    margin: 20px auto;
  }
}
```
これでカーソルの周りが光るスポットライトの完成です。

# 参考サイト

https://developer.mozilla.org/ja/docs/Web/API/Element/mousemove_event
https://developer.mozilla.org/ja/docs/Web/API/Element/setAttribute
https://developer.mozilla.org/ja/docs/Web/API/EventTarget/addEventListener
https://developer.mozilla.org/ja/docs/Web/API/MouseEvent/clientX
https://developer.mozilla.org/ja/docs/Web/CSS/background
https://developer.mozilla.org/ja/docs/Web/CSS/radial-gradient()
http://www.htmq.com/css3/radial-gradient.shtml
https://qiita.com/yukiB/items/31a9e9e600dfb1f34f76