マウスはどこにいるのか?
下記の投稿では、mousemoveイベントを使ってマウスの座標を取得していた。
しかし、この投稿で使っていたコードは、マウスの座標を正確には得ることができない。
座標のいろいろ
mousemove(およびほかのマウス関連のイベント)をaddEventListener()でキャッチした時、MouseEventが返される。
element.addEventListener("mousemove", (ev) => { // evがMouseEventクラス
    let x = ev.offsetX;   // evからX座標を取得
    let y = ev,offsetY;   // evからY座標を取得
  // xとyを使って、いろいろな処理を行う。
});  MouseEventから取得できる座標には以下の種類がある。
| プロパティ名 | 説明 | 
|---|---|
| clientX | イベントが発生した時点のアプリケーションのビューポートにおける水平座標 | 
| offsetX | このイベントと対象ノードのパディング辺との間のマウスポインターの X 座標におけるオフセット | 
| pageX | クリックされた位置の X(水平)座標を、文書全体の左端からの相対座標 | 
| screenX | グローバル(スクリーン)座標における、マウスポインターの水平方向の座標(オフセット) | 
| x | clientXの別名 | 
の各プロパティの説明を書きだしてみたが、書き方が統一されていないのでさっぱりわからない…。
そこで次のようなHTMLとTypeScriptを使って調べてみた。
<canvas id="canvas1" width="50" height="50"></canvas>
<div id="message1" class="message"></div>window.addEventListener("DOMContentLoaded", () => {
        let canvas = document.getElementById("canvas1") as HTMLCanvasElement;
        let message = document.getElementById("message1") as HTMLDivElement;
        DrawCanvas(canvas);
        canvas.addEventListener("mousemove", (ev) => {
            let text = "";
            text += "screenX:" + ev.screenX + " ";
            text += "screenY:" + ev.screenY + "\n";
            text += "pageX:" + ev.pageX + " ";
            text += "pageY:" + ev.pageY + "\n";
            text += "offsetX:" + ev.offsetX + " ";
            text += "offsetY:" + ev.offsetY + "\n";
            text += "clientX:" + ev.clientX + " ";
            text += "clientY:" + ev.clientY + "\n";
            message.innerText = text;
        });
    }
});
// DrawCanvas()のコードは省略するが、canvasの中心を通る縦線と横線を書いている。
// CANVASのサイズが50,50なので、
// (0,25)->(50,25)の横線と、(25,0)->(25,50)の縦線を書いていて、(25,25)が中心になる。これを使って、CANVAS上でマウスを動かしたり、CANVAS上の十字にポインタを合わせてみた。
そうして分かったことは、以下の通り。
- ScreenX/Yはディスプレイの左上を原点とした絶対座標系- そのため、ブラウザを動かすとScreenX/Yも変わる。
 
- ClientX/Yはブラウザの見えている部分(クライアント領域)の左上を原点とした座標系
- PageX/Yは表示されているページ全体(スクロールして見えていない部分も含む)の左上を原点とした座標系- ページ全体が大きくてスクロールバーが出ているときなどに、スクロールするとClientX/Yは座標が変化する。PageX/Yは変化しない。
- スクロールがなかったり、もっとも左上にスクロールしていれば、ClientX/YとPageX/Yは一致する。
 
- offsetX/Yは、CANVASの左上を原点とした座標系。- 当然、ブラウザを動かしても、スクロールしても、座標は変化しない。
 
offsetX/Yの問題点
以降、TypeScriptでCANVASに描画するときに使う座標をCANVAS座標と呼ぶことにする。
そのCANVAS座標とマッチするようにマウス座標を取得するにはoffsetX/Yを使えばよい。…ように見えるが、隠された問題が2つある。それはCSSのpaddingとwidth、heightである。
問題点1 padding
そして、つぎのようなCSSを設定して、CANVASにpaddingをつけてみる。
#canvas1 {
        padding: 10px 50px;
}すると、CANVASの中心は、CANVAS座標では(25,25)であるが、offsetX,offsetYは(75,35)になる。paddingによって表示が(50,10)ズレているが、その値だけoffsetX,offsetがズレるのだ。
(このことは、最初に示したoffsetXの説明にも「パディング辺との間のマウスポインターの X 座標」と書かれてある。padding-leftの分が足されているのだ。)
問題点2 heightとwidth
つぎのようなCSSを設定して、CANVASにwidthをつけてみる。
#canvas1 {
        width: 100px;
}画面に表示されるCANVASはこのCSSが無いときの2倍になる。
HTMLの方では<canvas width="50">となっているので、CANVAS座標としては横幅は50であるが、画面に表示されるCANVASはCSSによって横幅100になり、2倍に拡大されて表示されるからである。
この状態でマウスポインタを中心に合わせると、offsetX,offsetYは(50,50)になる。表示が2倍に拡大されたことによって、マウス座標とCANVAS座標も2倍の違いが生じる。
offset座標からCANVAS座標への変換
したがって、MouseEventによってoffsetX,offsetYを得たあと、次のような変換処理が必要である。
/**
 * マウスイベントで得たoffset座標をcanvas座標に変換する。
 * @param canvas 
 * @param offset 
 */
function OffsetToCanvas(canvas: HTMLCanvasElement, offset: { offsetX: number, offsetY: number }) {
    let style = window.getComputedStyle(canvas);
    let stylePaddingLeft = Number(style.paddingLeft.replace("px", ""));
    let stylePaddingTop = Number(style.paddingTop.replace("px", ""));
    let stylePaddingRight = Number(style.paddingRight.replace("px", ""));
    let stylePaddingBottom = Number(style.paddingBottom.replace("px", ""));
    let styleWidth = canvas.clientWidth - stylePaddingLeft - stylePaddingRight;
    let styleHeight = canvas.clientHeight - stylePaddingTop - stylePaddingBottom;
    let x = (offset.offsetX - stylePaddingLeft) * canvas.width / styleWidth;
    let y = (offset.offsetY - stylePaddingTop) * canvas.height / styleHeight;
    return { X: x, Y: y }
}もちろんpaddingやwidthなどを指定しなければこのような変換は必要がない。
ただし、paddingは0にした方がよいが、widthは変更できるようにしたほうが便利かもしれない。そうすれば気軽にキャンバスを拡大/縮小表示することができる。例えばパソコンとスマートフォンではスクリーンサイズが異なるため、CSSだけで表示サイズを変えることができれば楽である。
実際の例
実際にいくつかの例を以下に示す。
CANVAS上をマウスが通過すると、そのときの様々な座標が右側に表示される。
Adjestedが、上で示した変換ざれた座標。それ以外はMouseEventの各種プロパティの座標である。
また1つ目はpaddingやwidthがなし、2つ目はpaddingがあり、3つ目はpaddingとborderがあり、4つ目はwidth指定で2倍に拡大されている。
4つのCANVASの中心座標は(25,25)になるので、そこにマウスポインタを合わせたときに、各座標がどのような値になるのかを確認して欲しい。
 
  
  
  
  

コメント