Hammer.JSでオプション調整をスワイプ・ピンチ・ローテートで行えるように変更

スマホ重視のUIを考えると、やはりゲージ操作でなくてcanvas上をスワイプ・ピンチ・ローテートして編集できた方が、直感的で良いのかなと思い変更してみました。

タッチイベントハンドラのライブラリ

Hammer.JS – Hammer.js

こちらを利用させていただきました。
eventオブジェクトに色々値が入ってくるので、
それを見て使いたい数値を拾ってオプションの値に入れるようにしました。

ドキュメントもありますが、GitHubのこのへんがサンプルなので、
ソースを参考にしつつ実装してみました。

本当は、QuoJSの方を先に試していたのですが、元々実装していたいろんなイベントに干渉したっぽくて辞めました。
基本的な挙動や機能に関しては、たぶんこっちの方が良さそうではあります。

使い方

hammer.jsを読み込みます。
コンパクト版だと18kB程度です。
余談ですが、hammer.min.mapを読み込む指定が入っているので、同じディレクトリに.mapファイルも置いておかないとサーバーにNot foundエラーログが溜まりまくります。

<script src="./js/hammer.min.js"></script>

イベントをバインドする場合は、以下のようにします。

var el = document.querySelector("#hammerDOM");  /* 対象の要素のセレクタ */
var mc = new Hammer(el);  /* Hammerオブジェクトを作成する */

/* デフォルトだとpinchとrotateが無効なので、使えるように設定する */
mc.get('pinch').set({ enable: true });
mc.get('rotate').set({ enable: true });

/* panの threshold を調整する */
mc.get('pan').set({ threshold: 1 });

/* バインドするイベントのタイプとコールバック関数を指定する */
mc.on("pan pinch rotate",function(event) { console.log(event) });

こうすると、hammerDOMというidの要素上で、パン(ドラッグ操作)、ピンチ、ローテート操作を行うと、コンソールログに出力するようになります。

なお、イベント検知の初期値の設定もあるので、動かしてみて引っかかる・むしろ引っかけたい(一定量以上で反応させたい)場合は、この時に設定してあげると良いです。thresholdというパラメータがあります。
特に、panは初期値10なので、低めにしてあげる方が良いのではと思います。

取得できるパラメータ

この時のeventオブジェクトに入っている情報は、以下になります。
※先程のサンプルのlog.htmlで、適当な場所をクリックした時のログ。

pointers: Array(1):[object MouseEvent]
changedPointers: Array(1):[object MouseEvent]
pointerType: mouse
srcEvent: [object MouseEvent]
isFirst: false
isFinal: true
eventType: 4
center: [object Object]
timeStamp: 1433512585636
deltaTime: 71
angle: 0
distance: 0
deltaX: 0
deltaY: 0
offsetDirection: 1
scale: 1
rotation: 0
velocity: 0
velocityX: 0
velocityY: 0
direction: 1
target: [object HTMLSpanElement] (<span id="target" class="bg5" style="display: bloc...)
tapCount: 1
type: tap
preventDefault: function (){b.srcEvent.preventDefault()}

iconDecotterで主に重要なのはこのあたり。

  • type:
    イベントのタイプ。tap, doubletap, pan, swipe, press, pinch ,rotateが指定できる。
    動作の開始・終了・キャンセル時の指定もある。多いのでドキュメント参照
  • eventType:
    typeの細分化というか、動作の定義値。例えば、panも細かく分けるとpanstart,panendなどがあって、panstartだとここに1、panendだと4が入る。
  • deltaX,deltaY:
    タッチ始点からのX座標・Y座標の変動値。直前の動作からの変化量はvelocityの方。
  • scale:
    ピンチ開始時点を1として、拡大・縮小の値。指を離した後、またピンチを始めると再度1から計算される。
  • rotation:
    ローテート開始時を0として、時計回りを正、反時計回りを負として、度数で回転量を取得。指を離した後、またローテートを始めると再度0から計算される。

移動はdeltaX,deltaY、拡大縮小はscale、回転はrotationの値を、
それぞれpan、pinch、rotateのイベント時に拾ってパラメータ調整処理に渡せば、
既存のオプション調整動作に合流して処理が行えるようになりました。

イベントを拾ってタイプごとに処理する

/* 引き続き#hammerDOMにバインド */
mc.on("tap pan pinch rotate",function(event) { getHammerData(event) });

/* #hammerDOM上のイベントを拾って処理する */
function getHammerData(event) {
  var ges_type = event.type;  /* ジェスチャーのタイプ */
  switch(ges_type) {
    case 'pan':
     /* panの時の処理 */
     var dx = event.deltaX;  /*タッチ始点からのX座標の移動量*/
     var dy = event.deltaY;  /*タッチ始点からのY座標の移動量*/
    break;
    case 'pinch':
     /* pinchの時の処理 */
     var scale = event.scale;  /*タッチ始点からの拡縮の変化量*/
    break;
    case 'rotate':
     /* rotateの時の処理 */
     var r = event.rotation;  /*タッチ始点からの回転角度の変化量*/
    break;
  }
}

こんな感じで、各ジェスチャーのイベントごとに値を取得し、いろいろと処理をすることでタッチ操作を実装できました。
なお、元々拡大縮小は左上を起点にしていましたが、このままだとピンチ操作の拡大縮小としてはちょっと直感的ではないので、フレーム画像の中心を起点に拡大縮小になるように変更しました。

その他

これに伴っていろいろ変えたことのメモなど。

オプション調整仕様

パラメータの変動を、そのままcanvasに反映させていたところを、一旦canvasに被せたガイド用レイヤーのCSSでエミュレートして、その結果をcanvasに反映するという方式にしました。

元々以前からAndroid端末ではそのようにしていたのですが、タッチ操作でも上記方式を取るようにしました。
理由としては、タッチ操作に合わせてcanvas描画すると非常に重いっぽいからです。
というか、inputのrange操作でもAndroidではすごく重かったので、そっちは先行してエミュレート式にしてたのですが、iPhoneでも動作が気まずい感じになっていたので、Android側の挙動を流用しました。

タッチ操作をバインドした要素の下層に別の要素がある場合

今回の実装では、タッチ操作イベントをバインドするのは、そのガイド用レイヤーの更に上に被せたコントローラーレイヤーにしています。エミュレート用のレイヤーが複数構成のため、そっちにバインドすると値が上手く取れなかったりしたためです。
ただその際、エミュレート用のレイヤーには user-select: none を指定しないと、そっちのレイヤが選択状態になって、マウス操作では一旦クリックしないとイベント発火しない状態になってしまいました。
つまるところ、ジェスチャー用の要素が他要素に重なるときは、下層にも user-select: none が必要ですね、ということです。

※バインドした要素そのものには、自動的に user-select: none が追加されるので、CSSで指定しなくても良いようでした。

コメント

タイトルとURLをコピーしました