カーソルを特定要素にホバーすると画像を表示するマウスストーカーのサンプル

マウスストーカーが盛んに取り上げられるようになって早数年。さすがにそろそろやり方くらいは……と思い、試してみることにしました。

コード

まずは成果物を。

最初の方(要素ホバーで拡大)は参考コードほぼそのままです。次のものはマウスストーカーが大きくなるだけでなく、背景画像をスポットライト的に切り抜いて表示する機能も付けたものになります。

リポジトリ。

HTML

<div class="c-mouseStalker_wrapper">
    <div class="c-mouseStalker">
        <div class="c-mouseStalker_cursor" id="c-mouseStalker_cursor"></div>
        <div class="c-mouseStalker_delay" id="c-mouseStalker_delay"></div>
    </div>
</div>

HTMLはいたってシンプル。カーソル用の要素とディレイがかかって追いかけてくる要素の2つの div を用意します。

今回はさらにラッパーで覆って overflow: hidden; をかけることで、画面端にカーソルが移動した際にディレイ要素の大きさ分だけはみ出てスクロールバーが表示されないようにしました。

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js" defer></script>

それから、今回はサクッと試すために CDN で TweenMax を読み込むようにしました。

css(Scss)

body {
    position: relative;
    // 元々のマウスカーソルを消す
    cursor: none;
}

.c-mouseStalker {
    // イベント反応させなくする
    pointer-events: none;
    width: 100%;
    height: 100%;
    position: absolute;
    top: 0;
    left: 0;
    &_wrapper {
        // イベント反応させなくする
        pointer-events: none;
        width: 100%;
        height: 100%;
        position: absolute;
        top: 0;
        left: 0;
        // 端にカーソルを持って行ったときにディレイの部分だけはみ出てスクロールバーが表示されてしまうのを抑止
        overflow: hidden;
    }
    &_cursor,
    &_delay {
        position: absolute;
        top: 0;
        left: 0;
        pointer-events: none; // イベント反応させなくする
    }
    &_cursor {
        width: 0.5rem;
        height: 0.5rem;
        background-color: f.$color;
        z-index: 10001;
        border-radius: 50%;
    }
    &_delay {
        $delayRadius: 100px / 4;
        width: 100%;
        height: 100%;
        background-color: f.$main-color;
        z-index: 10000;
        transition: clip-path ease 0.1s; // 遅くするともっさりした感じになる
        opacity: 0.4;
        // at 以降は円の中心の定義
        clip-path: ellipse($delayRadius $delayRadius at 50% 50%);
        &.active {
            $activeTimes: 4;
            background: {
                image: url("../img/img.jpg");
                attachment: fixed;
                size: cover;
                position: center center;
            }
            opacity: 1;
            clip-path: ellipse($delayRadius * $activeTimes $delayRadius * $activeTimes at 50% 50%);
        }
    }
}
  • bodycursor: none;: デフォルトのマウスカーソルを非表示に
  • .c-mouseStalker.c-mouseStalker_wrapper:
    • pointer-events: none;
    • どちらも表示画面サイズいっぱいまで拡大
  • .c-mouseStalker_wrapper: overflow: hidden; で端にカーソルを持って行ったときにディレイの部分だけはみ出てスクロールバーが表示されてしまうのを抑止
  • .c-mouseStalker_delay: 肝
    • 要素自体は透明。 clip-path で切り取った円形の範囲のみ見える状態にする
      • デフォルトは単色半透明のマスク
      • 特定要素にホバーしたとき、 jQuery でクラス付与 (ホバー解除時にクラスも削除)
        • クラスが付与されたとき、背景を単色から background-image で指定した画像にする
          • 背景画像は background-attachment, background-size, background-position 指定あり
    • transition: clip-path ease 0.1s; でホバー時の拡大縮小等の切り替えを easing
      • 0.3s にすると TweenMax のディレイも相まってもっさりした動きになってしまうので、 0.1s

JavaScript (jQuery + TweenMax)

// mouse stalker
const mouseStalker = () => {
    const $cursor = $('#c-mouseStalker_cursor');
    const $delay = $('#c-mouseStalker_delay');
    const paramsArray = {
        cursor: {
            width: $cursor.outerWidth(),
            coorX: 0,
            coorY: 0,
            delay: 0.001,
        },
        delay: {
            width: $delay.outerWidth(),
            coorX: 0,
            coorY: 0,
            delay: 6,
        },
    };
    const activeClass = 'active';
    let clipRadius = 100 / 4;
    let clipScale = 1;
    let clipPathCoor = `ellipse(${clipRadius * clipScale}px ${clipRadius * clipScale}px at 50% 50%)`;
    // カーソルの遅延アニメーション
    // ほんの少しだけ遅延させる (0.001秒)
    TweenMax.to(
        {},
        paramsArray.cursor.delay,
        {
            repeat: -1,
            onRepeat: function() {
                paramsArray.delay.coorX += (paramsArray.cursor.coorX - paramsArray.delay.coorX) / paramsArray.delay.delay;
                paramsArray.delay.coorY += (paramsArray.cursor.coorY - paramsArray.delay.coorY) / paramsArray.delay.delay;

                clipPathCoor = `ellipse(${clipRadius * clipScale}px ${clipRadius * clipScale}px at ${paramsArray.delay.coorX}px ${paramsArray.delay.coorY}px)`;
                // delay
                TweenMax.set(
                    $delay,
                    {
                        css: {
                            clipPath: clipPathCoor,
                        }
                    }
                );

                // cursor
                TweenMax.set(
                    $cursor,
                    {
                        css: {
                            left: paramsArray.cursor.coorX - (paramsArray.cursor.width / 2),
                            top: paramsArray.cursor.coorY - (paramsArray.cursor.width / 2),
                        }
                    }
                );
            }
        }
    );

    // mouse hover
    $('#hoverElmID').on({
        mouseenter: function () {
            $cursor.addClass(activeClass);
            $delay.addClass(activeClass);
            clipScale = 4;
        },
        mouseleave: function () {
            $cursor.removeClass(activeClass);
            $delay.removeClass(activeClass);
            clipScale = 1;
        },
    });

    // get mouse coordinate
    $(document).on('mousemove', function (e) {
        paramsArray.cursor.coorX = e.pageX;
        paramsArray.cursor.coorY = e.pageY;
    });
};

window.addEventListener('load', () => {
    mouseStalker();
});
  • だいたいサンプルコードに則ったコード
  • カーソルとディレイ要素の各種パラメータは別の変数で持っていると個人的に分かりづらかったのでオブジェクトにまとめました
  • 変数 clipPathCoor で指定した値が実際の clip-path で切り取られるディレイ要素の指定
    • カーソル用はほぼ弄らず
    • ディレイ要素の方は TweenMax の to で指定するパラメータが top, left から clip-path のみに変更
  • 特定要素のホバー時に上述 Scss の指定が反映されるようにクラスの付け外しを実施
    • 同時に clip-path で切り取られる範囲を拡大縮小(拡大率の数値を変更している)

今回はこれで望んだ挙動になりました。

参考

マウスストーカー

背景画像切り抜き

要素内座標(未使用)

この記事を書いた人

アルム=バンド

フロントエンド・バックエンド・サーバエンジニア。LAMPやNodeからWP、Gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。