JavaScript タッチイベントの取得(マルチ対応)サンプルコード
マウスイベントでは1つの座標しか取れませんが、タッチイベントは同時に複数の座標を取得することができることが知られています。特に、スマホやダブレットでは複数の座標を取得することでUIの使いやすさを向上しているケースがよくみられるようになりました。
複数の座標が取れるようになると、画像の拡大縮小、回転などのUIに応用できると思います。
このエントリで解説しているコードの動作画面はこんな感じです。
ご覧のように、複数の指のタッチイベントを同時に受けて絵を描いています。
スマホで起動しているため、画面が小さくてすみません。
0. まずはググってみる
のですが、簡潔で理解しやすいタッチイベント(マルチ)のコードがなかなか見つからなくて、結局は以下のページがいちばん参考になりました。
1. で、最初のハマりポイントは
タッチで取得できるイベントは、なぜか Array ではなく Object、、、
なので、
for ( const tch of _evt.changedTouches ) ...
と、簡潔には書けなくて昔ながらの i で回す
for ( let i=0 ; i<_evt.changedTouches.length ; i++ )
const tch = _evt.changedTouches[i]
書き方を強いられます。
2. もらえる座標
タッチイベントで渡ってくる情報って、_ev.pageX, _ev.pageY でして、、、ちょっと使いにくいです。具体的には「そのまま canvas に描くとマージンの分描画位置がズレる」という問題があります。参考にさせていただいた元のコードからこの部分は特に対処していないようで、どうしたものか、、、現在のところ放置中です。_ev.offsetX, _ev.offsetY がほしいなぁ、、、
なにか、よい方法をご存知でしたら、募集させてください。よろしくお願いいたします。
3. 結果
リファクタリングというか、ほぼほぼ書き直して出来上がったものがこちら。
See the Pen Javascript-touch-event-test by yamazaki.3104 (@yamazaki3104) on CodePen.
ご注意:
・スマホのブラウザで確認しないとタッチイベントは発生しないことにご注意ください
・PCのブラウザで起動した場合でも動作が確認できるように mousemove イベントのみ拾っていますが、あくまで確認用(メインのコードではない)なのでこちらもご注意ください
どうでしょうか。スマホで見ていただければ、最初の動画のように、複数の座標が取れて、同時に描画できていることが確認できるかと思われます。
4. 全コード
CodePen からもコードは確認できると思いますが、念のため、こちらにも同じものを載せておきます。読みやすい方をご覧ください。
<!DOCTYPE html>
<html><body>
<canvas id="canvas" width="600" height="600" style="border:solid black 1px;">
Your browser does not support canvas element.
</canvas>
<script>
class PointTable // 描画用の色と座標を覚えるためのクラス
{
constructor( _table_max = 10 ) {
this.color = []
this.x = []
this.y = []
this.table_max = _table_max
for ( let i=0 ; i<_table_max ; i++ ) {
this.color.push( `hsla( ${ i / this.table_max * 360 }, 100%, 33%, 0.8 )` )
this.x.push( 0 )
this.y.push( 0 )
}
}
get_idx( _i ) {
const i = ( _i < 0 ? -_i : _i ) // abs
return i % this.table_max
}
get_color( _tc ) {
const r = this.get_idx( _tc.identifier )
return this.color[r]
}
get_xy( _tc ) {
const r = this.get_idx( _tc.identifier )
return { x: this.x[r], y: this.y[r] }
}
set_xy( _tc ){
const r = this.get_idx( _tc.identifier )
this.x[r] = _tc.pageX
this.y[r] = _tc.pageY
}
}
const xy_tbl = new PointTable()
const elm = document.querySelector( 'canvas#canvas' )
const ctx = elm.getContext( '2d' )
// changedTouches を回す。 Array ではなく Object なんですよ。 Why?
const for_touches = ( _evt, _fnc ) => {
for ( let i=0 ; i<_evt.changedTouches.length ; i++ )
_fnc( _evt.changedTouches[i] )
}
const ctx_draw_line = ( _t ) => { // ctxに線を描く
const p = xy_tbl.get_xy( _t )
ctx.beginPath()
ctx.moveTo( p.x, p.y )
ctx.lineTo( _t.pageX, _t.pageY )
ctx.lineWidth = 4
ctx.strokeStyle = xy_tbl.get_color( _t )
ctx.stroke()
xy_tbl.set_xy( _t )
}
elm.addEventListener( "touchstart", ( _evt ) => {
_evt.preventDefault()
for_touches( _evt, ( _t ) => xy_tbl.set_xy( _t ) )
}, false )
elm.addEventListener( "touchmove", ( _evt ) => {
_evt.preventDefault()
for_touches( _evt, ( _t ) => ctx_draw_line( _t ) )
}, false )
elm.addEventListener( "touchend", ( _evt ) => {
_evt.preventDefault()
for_touches( _evt, ( _t ) => {
ctx_draw_line( _t )
ctx.fillStyle = xy_tbl.get_color( _t )
ctx.fillRect( _t.pageX - 4, _t.pageY - 4, 8, 8 )
})
}, false )
//----------------
// PC用 mousemove イベントでの動作確認コード
elm.addEventListener( "mousemove", ( _evt ) => {
_evt.preventDefault()
ctx_draw_line( { identifier: 0, pageX: _evt.pageX, pageY: _evt.pageY } )
}, false )
</script>
</body>
</html>
そんなに長くはないので、、、
まとめ
コードを見ていただければ、かなり短いし、ここ(Qiita)を読んでいるレベルの方々に、特に解説するほどではないいけど、
・changedTouches とかに複数の情報がまとまって渡っていくるところがタッチイベントのポイント
・で、changedTouches は Array ではなく Object なところもポイント(Arrayにしてほしいなぁ もっと簡潔に書きたいから)
・あとは、offsetX, offsetY が「無い」ところもポイントかな(いつかサポートされるのでしょうか、、、遠い目)
・情報保存用のクラス、PointTable の _table_max は 10 にしてるけど、もっと少なくてもいいかもしれない。3点以上来てもどう処理していいかわからないしね。
このコードが、みなさまのプログラミングライフの一助になれば幸いです。