VanillaJSで3パターンのマウスストーカー作ってみた!
参考サイト
いきなりですけど、このサイトめっちゃオサレじゃないですか!?(勢い)
是非とも、全部真似したい!
真似させて頂きたい!!!!
ということで、真似させて頂きました。
結果
ひとまず完成図がこちら。
ホバーしたら背景画像が見える(ように見える)やつ
ホバーしたら英語から日本語になるやつ
ホバーしたら画像出てくるやつ
解説
まずは、超シンプルなマウスストーカーからやっていきます。
シンプルストーカーのコード全体像
<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';
}
マウスストーカー作成の基本構造
- マウスストーカーを発火させるエリアを指定。
- 指定したエリアに「mousemove」イベントを登録し、イベント内でカーソル位置を取得
- 取得したカーソル位置を、マウスストーカー用の要素に反映
の、流れになります。
1.マウスストーカーを発火させるエリアを指定。
今回作成のシンプルストーカーの場合は、window全体なので
const area = window;
2.指定したエリアに「mousemove」イベントを登録し、イベント内でカーソル位置を取得
自分的に、ここが初耳学だったのですが
JSのイベントを登録・実行した際には、「イベントオブジェクト」というものが引数に渡されるそうです。
この中にイベントの発生状況に関する情報が詰まっていて
イベントの種類によってどんなオブジェクトが参照できて、どんな情報を取得できるかが変わります。
例えば、今回の「mousemove」イベントでは「MouseEvent」を使用できます。
以下先人のありがたき知恵なので、ご確認下さい。
そんなこんなで、カーソル位置を取得します。
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;
}
解説
実装の流れは以下です。
- マウスストーカーを発火させるエリアを指定。
- 指定したエリアに「mousemove」イベントを登録し、イベント内でカーソル位置を取得
- 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);
})
実装の流れ
- マウスストーカーを発火させるエリアを指定。
- 指定したエリアに「mousemove」イベントを登録し、イベント内でカーソル位置を取得
- 取得したカーソル位置を、マウスストーカー用の要素に反映
流れは、一つ目のマウスストーカーと同じです。
めちゃくちゃアナログに、英語要素の上に日本語要素を重ねて
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');
})
}
解説
実装の流れは以下です。
- マウスストーカーを発火させるエリアを指定。
- 「mouseStalker」にデフォルトスタイルを当てる
- 指定したエリアに「mousemove」イベントを登録し、イベント内でカーソル位置を取得
- 取得したカーソル位置をマウスストーカー用の要素に反映し、背景画像を変更する
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