アキレスの亀で
マウス追従
ポインタ作成
アキレスと亀の理論で
マウス追従スクリプトを作る

image: アキレスの亀でマウス追従ポインタ作成
MouseTracer with Achilles tortoise

アキレスと亀理論はマウス追従スクリプトには意外と役に立つ

 | 2023/04/05

開発
動機

マウスを追従するポインタとは
  • マウス追従ポインタの名称

    マウスに追従するポインター、昔からありますね。
    通常はマウスストーカーと呼ばれているようです。ただ、ストーカーだと距離をとって場所を追いかけ回すようなイメージですね。
    今回作成するのはそれよりもっと細かな、マウスの動きそのものをトレースするポインタです。
    なのでトレーサーと命名します。
    下記のようなスクリプトです。

    マウススピードに応じて変形するマウストレーサー。:https://labo.studio-happyvalley.com/mouseTracer/

マウスを追従するポインタはアキレスの亀に似ている
  • 追従の考え方:基本

    追従の考え方と必要なことは、『常時マウスの座標を取得するメソッド』と『一定間隔で発生するイベントメソッド』になります。
    流れは…
    1. マウスが移動した時、1回目のイベントが発生しマウスと追従者の座標の差分を計算、同時に一定間隔で発生するイベントも発動。
    2. 次の一定間隔のイベント発生時に追従者へ差分をプラスし直前のマウス移動させます。同時にその時点のマウスの座標も取得し差分を計算。
    3. 上記の処理を繰り返すことでマウスを追従するポインターを実現。

    直前にマウスがいた座標へポインタ―がたどり着いた時、その間にマウスはさらに先へいっている、この繰り返し。
    言い方を変えるといつまでたっても追従者はマウスを追い越せないみたいで、まるでアキレスの亀みたいです。
    実際、追従者はマウスに追いつけても追い越すことはできませんからね。

    fig1-11

  • 追従するポインタ―のスピード調整

    ただ上記のままだとマウスのすぐ後ろをピッタリ金魚のうんこのようにくっついてる感じになるので見映えが面白くありません。
    そのため時間差が必要になります。つまり、マウスに追いつける回数を1回から複数回に変更することで見映えのいい動きになります。
    例えば下記の図は5回で一回目のまうすに追いつく時間差になります。5回ということは差分距離に1/5なので係数として0.2掛ければいいことになります。
    さらに、この係数を複数用意すれば追従者を複数にすることも可能です。

    fig2-7

  • マウス座標を取得する間隔

    さて、基本の追従の考え方はOKとしてあとは一定間隔で発生するスクリプトをどうするかです。
    まずマウスの軌跡を精密にきれいにトレースするためには非常に細かな間隔で発生するイベントが必要になります。
    下記はマウスを1秒間シュッと動かしたイメージの軌跡を1秒間に10回座標を取得する場合のイメージになります。
    下記のグラフを見れば1/10秒程度の間隔では曲線が歪(いびつ)なのがよくわかると思います。

    fig3-5

一定間隔にイベントを発生させるのに最適なメソッド:RequestAnimationFrame
  • RequestAnimationFrameメソッドは1秒間に60回

    昔からよく使うのはsetIntervalやsetTimeoutの入れ子ですが実験してみると仕様的に1秒間に20回くらいが限度の様です。
    1秒間に60回という驚異的なイベント発生が可能なのがRequestAnimationFrameです。
    firefoxでは非常に正確に1秒間に60回発生します。なのでこのメソッドを使用します。

実践

シンプルなスクリプトでワークショップ
  • 追従の考え方:基本

    まず、マウス追従に必要なオブジェクトを初期化・宣言します。
    1.マウス
    2.マウス追従ポインタ:名前をtracerとします。

    const tracer = {
    	e: document.querySelector('.tracer'),
    	x: 0,
    	y: 0,
    };
    const mouse = {
    	x: 0,
    	y: 0
    };
  • 次にイベントを設定

    次にマウス座標を随時取得するためのイベントを設定します。
    現状、デバイスがマウスだけとは限らないためpointerEventを使用しています。

    function pointermove(e) {
    	mouse.x = e.clientX;
    	mouse.y = e.clientY;
    }
    document.addEventListener('pointermove', pointermove);
    
  • アキレスの亀方式でポインタの座標を設定

    また、ファイルを読み込み直後にオブジェクトの座標を更新するイベントと関数も設定します。
    関数名はupdate()とします。
    基本の考え方は上で話したアキレスの亀の考え方です。
    現在のマウスの座標からポインタの座標を引き算します。
    言い換えると直前のマウスの座標と現在のマウスの座標との差分になります。

    function update() {
    	tracer.x += mouse.x - tracer.x;
    	tracer.y += mouse.y - tracer.y;
    }
    document.addEventListener('DOMContentLoaded', update);
  • 追従するポインタ―のスピード調整

    上でも話したようにこのままでは単にポインタはマウスにくっついたままなので、マウスを遅れて追従するようにします。
    に追いつく回数を増やします。
    今回は5回くらいで追いつくようにするため0.2を掛けます。update関数を変更します。

    function update() {
    	// position update
    	tracer.x += (mouse.x - tracer.x) * 0.2;
    	tracer.y += (mouse.y - tracer.y) * 0.2;
    }
  • 一定間隔でイベントを発生させるRequestAnimationFrame

    1位秒間に約60回程度発生するメソッドRequestAnimationFrameを使用し、アニメーションさせます。
    translate3dを使用しなめらかに移動させます。数値も小数点を四捨五入し整数にします。

    function update() {
    	// position update
    	tracer.x += (mouse.x - tracer.x) * 0.2;
    	tracer.y += (mouse.y - tracer.y) * 0.2;
    
    	// rounding  
    	const x = Math.round(tracer.x);
    	const y = Math.round(tracer.y);
    
    	// set style
    	tracer.e.style.transform = `translate3d(${x}px, ${y}px, 0)`;
    	requestAnimationFrame(update);
    }
    
  • このワークショップの実装サンプルです

    シンプルなマウストレーサーのサンプル:https://labo.studio-happyvalley.com/mouseTracer/simple/

『アキレスの亀でマウス追従ポインタ作成』関連のお薦め

このページで紹介しているプログラムやビジュアルなどご依頼いただければ実装を賜ります。
お問い合わせはこちら