🐧

VanillaJSで3パターンのマウスストーカー作ってみた!

2022/08/19に公開

参考サイト

いきなりですけど、このサイトめっちゃオサレじゃないですか!?(勢い)

https://www.whiteoutworks.com/

是非とも、全部真似したい!
真似させて頂きたい!!!!
ということで、真似させて頂きました。

結果

ひとまず完成図がこちら。

ホバーしたら背景画像が見える(ように見える)やつ

https://codepen.io/con_ns_pgm/pen/JjLwaOJ

ホバーしたら英語から日本語になるやつ

https://codepen.io/con_ns_pgm/pen/RwMEYBY

ホバーしたら画像出てくるやつ

https://codepen.io/con_ns_pgm/pen/ZExVMNL

解説

まずは、超シンプルなマウスストーカーからやっていきます。

シンプルストーカーのコード全体像

<div id="mouseStalker"></div>
body{
  background-image:url("https://dl.dropbox.com/s/trv8n3bvp0yt94l/season1.jpg?dl=0")
}

#mouseStalker{
  position: absolute;
  top: 0;
  left: 0;
  width: 100px;
  height: 100px;
  border-radius: 50%;
  background-color: white;
  transition-timing-function: ease-out;
  z-index: 10;
}
const area = window;

//任意のエリアにカーソルが入って、動いたときにイベント発火
area.addEventListener('mousemove', function (e) {
  normalStalker(e);
})


function normalStalker(e) {
//イベントオブジェクトを参照し、カーソル位置情報を取得
  const mousePosX = e.clientX;
  const mousePosY = e.clientY;  
  const mouse = document.getElementById('mouseStalker');
  const mouseWidth = mouse.clientWidth;
  const cssPosAjust = mouseWidth / 2;
  const x = mousePosX - cssPosAjust;
  const y = mousePosY - cssPosAjust;

//カーソルの位置情報を「mouseStalker」に反映
  mouse.style.left = x + 'px';
  mouse.style.top = y + 'px';
}

マウスストーカー作成の基本構造

  1. マウスストーカーを発火させるエリアを指定。
  2. 指定したエリアに「mousemove」イベントを登録し、イベント内でカーソル位置を取得
  3. 取得したカーソル位置を、マウスストーカー用の要素に反映

の、流れになります。

1.マウスストーカーを発火させるエリアを指定。

今回作成のシンプルストーカーの場合は、window全体なので

const area = window;

2.指定したエリアに「mousemove」イベントを登録し、イベント内でカーソル位置を取得

自分的に、ここが初耳学だったのですが
JSのイベントを登録・実行した際には、「イベントオブジェクト」というものが引数に渡されるそうです。
この中にイベントの発生状況に関する情報が詰まっていて
イベントの種類によってどんなオブジェクトが参照できて、どんな情報を取得できるかが変わります。
例えば、今回の「mousemove」イベントでは「MouseEvent」を使用できます。
以下先人のありがたき知恵なので、ご確認下さい。

https://noumenon-th.net/programming/2017/06/24/eventobject/#:~:text=イベントオブジェクトは、イベントハンドラー,することができます。

https://www.javadrive.jp/javascript/event/index20.html

https://developer.mozilla.org/ja/docs/Web/API/Event

そんなこんなで、カーソル位置を取得します。

function normalStalker(e) {
  const mousePosX = e.clientX;
  const mousePosY = e.clientY;  
}

この部分ですね。この「e」ってやつがミソです。
これ書かないと、引数に渡されるイベントオブジェクトにアクセスできないので
一向にカーソル位置取得できません。

ついでに、normalstalker関数の中で他にごちゃごちゃやってることは何かと言うと
CSSでposition:absolute指定して中央配置するときにするやつ、ありますよね?

div{
position:absolute;
top:50%;
left:50%;
transform:translate(-50%,-50%);
}

こんなやつです。
これのイメージで、「mouseStalker」を取得したカーソル位置に動かすときに
同時に、要素の半分の距離戻してます。そうすることでカーソル位置が要素の中央にきます。

  const mouse = document.getElementById('mouseStalker');
  const mouseWidth = mouse.clientWidth;
  const cssPosAjust = mouseWidth / 2;
  const x = mousePosX - cssPosAjust;
  const y = mousePosY - cssPosAjust;

この部分です。

3.取得したカーソル位置を、マウスストーカー用の要素に反映

mouse.style.left = x + 'px';
mouse.style.top = y + 'px';

area.addEventListener('mousemove', function (e) {
  normalStalker(e);
})

これで、マウスストーカーを動かしてイベント内で関数実行すれば完成です。
晴れて「ただの動く丸」が実装されました!

ホバーしたら背景画像が見える(ように見える)やつ

お次は、これです。
これは、いろんなやり方があるみたいで
正解はないですが、パフォーマンスに違いはあるかもしれません。
いつか、パフォーマンス比較の記事も書いてみたいと思いますが
今はちょっと、自分が書いたやつで精一杯なので勘弁して下さい。

まずはコード全体像

<div class="spring imgBox">
   <img src="https://dl.dropbox.com/s/trv8n3bvp0yt94l/season1.jpg?dl=0" alt="">
</div>
<div class="summer imgBox">
   <img src="https://dl.dropbox.com/s/ck7a0jxb2lukclv/season2.jpg?dl=0" alt="">
</div>
.summer{
    clip-path: ellipse(50px 50px at 0px 0px);
    transition:all 0.1s;
}

.imgBox{
  position: absolute;
  top:0;
  left:0;
  width:100vw;
  height:auto;
  z-index: 1;

  img{
    width:100%;
    height:auto;
  }
}
const area = window;

area.addEventListener('mousemove', function (e) {
  clipStalker(e);
})

function clipStalker(e) {
  const mousePosX = e.clientX;
  const mousePosY = e.clientY;
  const summer = document.querySelector('.summer');
  let clip = `ellipse(100px 100px at ${mousePosX}px ${mousePosY}px)`;
  summer.style.clipPath = clip;
}

解説

実装の流れは以下です。

  1. マウスストーカーを発火させるエリアを指定。
  2. 指定したエリアに「mousemove」イベントを登録し、イベント内でカーソル位置を取得
  3. clip-pathの位置と取得したカーソル位置を紐づける

こんな感じです。
1と2はシンプルストーカーの例と全く同じなので端折ります。
強いていうなら、画像の上でしかマウスストーカーは使わないので

const area = window;

ではなく
imgBoxを2つとも内包する「imgBoxContainer」を設けて

const area = document.querySelector('.imgBoxContainer');

とする方が良いかもしれません。

今回の実装のポイントは3です。
画像を2枚重ねて、上の画像をclip-pathで円形に切り抜いています。
最初にこのマウスストーカーを見た時は
桜の絵が1番上にあって、背景にある向日葵を透過しているのか。。?
と思っていたのですが、逆でした。

ということで、

let clip = `ellipse(100px 100px at ${mousePosX}px ${mousePosY}px)`;
summer.style.clipPath = clip;

この部分で、clip-pathの位置にカーソル位置を反映して、完成です。
今回の場合、clip-pathの特性から(指定した位置を中心に切り抜かれる)
半分戻す作業は不要になります。

ホバーしたら英語から日本語になるやつ

これは結構シンプルアナログです。

コード全体像

 <div class="translate">
   <div class="en clip">
     <p>Spring Spring Spring Spring Spring</p>
     <p>Summer Summer Summer Summer Summer</p>
     <p>Autumn Autumn Autumn Autumn Autumn</p>
     <p>Winter Winter Winter Winter Winter</p>
   </div>
   <div class="ja clip">
     <p>これはきっと「春」なんじゃないでしょうか</p>
     <p>これはきっと「夏」なんじゃないでしょうか</p>
     <p>これはきっと「秋」なんじゃないでしょうか</p>
     <p>これはきっと「冬」なんじゃないでしょうか</p>
   </div>
 </div>
.translate{
  position: relative;

  .en{
    position: absolute;
    top: 0;
    left: 0;
    font-size: 50px;
    background-color: rgba(rgb(200, 200, 200),0.4);
    color:grey;
  }
  
  .ja{
    position: absolute;
    top: 0;
    left: 0;
    font-size: 50px;
    background-color:white;
    clip-path: ellipse(30px 30px at 0px 0px);
    transition:all 0.1s;
  }
}
const area = window;

function translateStalker(e) {
  const mousePosX = e.clientX;
  const mousePosY = e.clientY;
  const ja = document.querySelector('.ja');
  let clip = `ellipse(50px 50px at ${mousePosX}px ${mousePosY}px)`;
  ja.style.clipPath = clip;
}

area.addEventListener('mousemove', function (e) {
  translateStalker(e);
})

実装の流れ

  1. マウスストーカーを発火させるエリアを指定。
  2. 指定したエリアに「mousemove」イベントを登録し、イベント内でカーソル位置を取得
  3. 取得したカーソル位置を、マウスストーカー用の要素に反映

流れは、一つ目のマウスストーカーと同じです。
めちゃくちゃアナログに、英語要素の上に日本語要素を重ねて
clip-pathで切り抜いてます。
ちょっと気をつけないといけないのは、英語と日本語の文字数が違うので
横にはみ出たりしちゃうところです。ここは良い感じに文字量調整で対応してください。
僕は、はみ出ました。

ホバーしたら画像出てくるやつ

コード全体像

<div class="hoverImgStalker">
  <div id="mouseStalker"></div>
  <div class="spring hovItem">
     <p>SPRING</p>
  </div>
  <div class="summer hovItem">
     <p>SUMMER</p>
  </div>
  <div class="autumn hovItem">
     <p>AUTUMN</p>
  </div>
  <div class="winter hovItem">
     <p>WINTER</p>
  </div>
</div>
.hoverImgStalker{
  .hovItem{
    padding:10px 0px 10px 30px;
    border-bottom: 1px solid grey;
    font-size: 50px;
    cursor: pointer;


    p{
      position: relative;
      z-index: 1;
      line-height: 1;
    }
  }

  #mouseStalker{
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;
    border-radius: 10px;
    background-size: cover;
    transition: opacity 0.1s;
    opacity: 0;
    width:250px;
    height:150px;
    
    
    &.is-hover{
      animation: showAnime 1s forwards;
      @keyframes showAnime {
        0%{
          opacity: 0;
        }
        100%{
          opacity: 1;
        }
      }
    }
    &.is-hidden{
      animation: hiddenAnime 0.4s forwards;
      @keyframes hiddenAnime {
        0%{
          opacity: 1;
        }
        100%{
          opacity: 0;
        }
      }
    }
  }
}

const area = document.querySelector('.hoverImgStalker');

area.addEventListener('mousemove', function (e) {
  hoverImgStalker(e);
})

function hoverImgStalker(e) {
  const mousePosX = e.clientX;
  const mousePosY = e.clientY;
  const stalker = document.getElementById('mouseStalker');
  const stalkerWidth = stalker.clientWidth;
  const stalkerHeight = stalker.clientHeight;
  const cssPosAjustWidth = stalkerWidth / 2;
  const cssPosAjustHeight = stalkerHeight / 2;
  const x = mousePosX - cssPosAjustWidth;
  const y = mousePosY - cssPosAjustHeight;

  stalker.style.left = x + 'px';
  stalker.style.top = y + 'px';

  const spring = document.querySelector('.spring');
  const summer = document.querySelector('.summer');
  const autumn = document.querySelector('.autumn');
  const winter = document.querySelector('.winter');

  spring.addEventListener('mouseover', function () {
    stalker.classList.remove('is-hidden');
    stalker.classList.add('is-hover');
    stalker.style.backgroundImage = 'url("https://dl.dropbox.com/s/trv8n3bvp0yt94l/season1.jpg?dl=0")';
  })
  spring.addEventListener('mouseout', function () {
    stalker.classList.add('is-hidden');
    stalker.classList.remove('is-hover');
  })

  summer.addEventListener('mouseover', function () {
    stalker.classList.remove('is-hidden');
    stalker.classList.add('is-hover');
    stalker.style.backgroundImage = 'url("https://dl.dropbox.com/s/ck7a0jxb2lukclv/season2.jpg?dl=0")';
  })
  summer.addEventListener('mouseout', function () {
    stalker.classList.remove('is-hover');
    stalker.classList.add('is-hidden');
  })

  autumn.addEventListener('mouseover', function () {
    stalker.classList.remove('is-hidden');
    stalker.classList.add('is-hover');
    stalker.style.backgroundImage = 'url("https://dl.dropbox.com/s/wd7z977038fpyt3/season3.jpg?dl=0")';
  })
  autumn.addEventListener('mouseout', function () {
    stalker.classList.add('is-hidden');
    stalker.classList.remove('is-hover');
  })

  winter.addEventListener('mouseover', function () {
    stalker.classList.add('is-hover');
    stalker.classList.remove('is-hidden');
    stalker.style.backgroundImage = 'url("https://dl.dropbox.com/s/ecbr2vdvb13kzoz/season4.jpg?dl=0")';
  })
  winter.addEventListener('mouseout', function () {
    stalker.classList.remove('is-hover');
    stalker.classList.add('is-hidden');
  })
}

解説

実装の流れは以下です。

  1. マウスストーカーを発火させるエリアを指定。
  2. 「mouseStalker」にデフォルトスタイルを当てる
  3. 指定したエリアに「mousemove」イベントを登録し、イベント内でカーソル位置を取得
  4. 取得したカーソル位置をマウスストーカー用の要素に反映し、背景画像を変更する

1.3は同様なので端折ります。

2.「mouseStalker」にデフォルトスタイルを当てる

  #mouseStalker{
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;
    border-radius: 10px;
    background-size: cover;
    transition: opacity 0.1s;
    opacity: 0;
    width:250px;
    height:150px;
    
    &.is-hover{
      animation: showAnime 1s forwards;
      @keyframes showAnime {
        0%{
          opacity: 0;
        }
        100%{
          opacity: 1;
        }
      }
    }
    &.is-hidden{
      animation: hiddenAnime 0.4s forwards;
      @keyframes hiddenAnime {
        0%{
          opacity: 1;
        }
        100%{
          opacity: 0;
        }
      }
    }
  }
}

この部分です。
「is-active」「is-hidden」クラスを設けているのは
ゆっくり消えて、ゆっくり出現させたいがためだけです。
なくても動きます。

4.取得したカーソル位置をマウスストーカー用の要素に反映し、背景画像を変更する

spring.addEventListener('mouseover', function () {
    stalker.classList.remove('is-hidden');
    stalker.classList.add('is-hover');
    stalker.style.backgroundImage = 'url("https://dl.dropbox.com/s/trv8n3bvp0yt94l/season1.jpg?dl=0")';
  })
  spring.addEventListener('mouseout', function () {
    stalker.classList.add('is-hidden');
    stalker.classList.remove('is-hover');
  })

この部分です。
ホバーするときは、背景画像を設定して、「is-hiddden」除去、「is-hover」付与
カーソルが外れる時はその逆です。ただし、背景画像は設定したままにします。
ここで背景画像を削除すると、パッと画像が消えてしまいます。
この作業を全ての要素行えば完成です。

Discussion