マウスはどこにいるのか?
下記の投稿では、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)になるので、そこにマウスポインタを合わせたときに、各座標がどのような値になるのかを確認して欲しい。
コメント