Touch/Gestureイベントの勘所
iPhone/iPad向けのWebアプリでタッチでの操作を扱う際のメモ。
ユーザによるズームは無効にしておいた方が良さそう。
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
【追記】
contentの区切りは","が正です
Touch
Touchイベントは
- touchstart :スクリーンに指が触れた
- touchmove :スクリーン上で指が動いてる最中
- touchend :指がスクリーンから離れた
- touchcancel :システムがタッチイベントをキャンセルした場合?
の4種類です。
注意しないといけないのは、例えばタッチした場所の座標を表示しようとしたとき
target.addEventListener('touchstart', touchStart, false); function touchStart(ev) { var x = ev.screenX; var y = ev.screenY; }
の様にやってしまいがちですが、これは動きません(x/yはundefindになる)。
Touchイベントは、タッチを検知するための
- touches : Touchオブジェクトの配列
- targetTouches : イベント発生元のTouchオブジェクトの配列
- changedTouches : 最後のTouchイベントから変更が起こっているTouchオブジェクトの配列
というプロパティを持っているので、こいつらに入っているTouchオブジェクトのプロパティを参照する必要があります。
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=no;" /> <title>test</title> <style> div { display: inline-block; margin: 0 10px; } div#target { display: block; width: 200px; height: 200px; background-color: blue; } </style> </head> <body> <h1>Gesture test.</h1> <div id="start"> <p>X:<span class="x"></span></p> <p>Y:<span class="y"></span></p> </div> <div id="move"> <p>X:<span class="x"></span></p> <p>Y:<span class="y"></span></p> </div> <div id="end"> <p>X:<span class="x"></span></p> <p>Y:<span class="y"></span></p> </div> <div id="target"></div> <script> var startx = document.querySelector('div#start span.x'), starty = document.querySelector('div#start span.y'), movex = document.querySelector('div#move span.x'), movey = document.querySelector('div#move span.y'), endx = document.querySelector('div#end span.x'), endy = document.querySelector('div#end span.y'), target = document.getElementById('target'); target.addEventListener('touchstart', touchStart, false); target.addEventListener('touchmove', touchMove, false); target.addEventListener('touchend', touchEnd, false); function touchStart(ev) { var touch = ev.touches[0]; startx.innerHTML = touch.screenX; starty.innerHTML = touch.screenY; } function touchMove(ev) { ev.preventDefault(); var touch = ev.changedTouches[0]; movex.innerHTML = touch.screenX; movey.innerHTML = touch.screenY; } function touchEnd(ev) { var touch = ev.changedTouches[0]; endx.innerHTML = touch.screenX; endy.innerHTML = touch.screenY; } </script> </body> </html>
ターゲットの要素の上でスクロールしたい時にページ自体がスクロールしてしまうので
touchMoveでpreventDefault()する様にします。
そもそも、ページ全体でスクロールさせたくない場合には
document.addEventListener('touchmove', function(ev) { ev.preventDefault(); }, false);
の様にします。
さらに、touchendイベントが起こっている時点で、touchesには何も無い状態なので、changedTouchesのTouchオブジェクトを使います。
Gestures
Gestureイベントはユーザが2本以上の指を使ってスクリーンに触れたときに発生します。
Touch Gesture Reference Guide
http://www.lukew.com/ff/entry.asp?1071
Touch/Gestureイベントは以下の様な流れで発生します。
- スクリーンにタッチ -> touchstart
- 他の指もスクリーンにタッチ(都合2本指でタッチ) -> gesturestart -> touchstart
- 指が動く -> gesturestart
- どちらかの指がスクリーンから離れる -> gestureend -> touchend
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=no;" /> <title>test</title> <style> div#log { border: 1px solid #333; width: 400px; height: 500px; margin-left: 50px; overflow: scroll; } div#target { display: inline-block; float: left; width: 200px; height: 200px; background-color: blue; } </style> </head> <body> <h1>Gesture test.</h1> <div id="target"></div> <div id="log"></div> <script> var log = document.getElementById('log'), target = document.getElementById('target'); target.addEventListener('touchstart', touchStart, false); target.addEventListener('touchmove', touchMove, false); target.addEventListener('touchend', touchEnd, false); target.addEventListener('gesturestart', gestureStart, false); target.addEventListener('gesturechange', gestureChange, false); target.addEventListener('gestureend', gestureEnd, false); function logger(msg) { var p = document.createElement('p'); var t = document.createTextNode(msg); p.appendChild(t); log.appendChild(p); } function touchStart(ev) { logger('touchstart'); } function touchMove(ev) { ev.preventDefault(); logger('touchMove'); } function touchEnd(ev) { logger('touchEnd'); } function gestureStart(ev) { logger('gestureStart'); } function gestureChange(ev) { logger('gestureChange'); } function gestureEnd(ev) { logger('gestureEnd'); } </script> </body> </html>
Gestureイベントが持っているプロパティでTouchイベントには無いものがevent.rotationとevent.scaleです。
要素を操作したい場合などにTouchイベントで2本の指をトラッキングするよりも簡単に扱う事ができます。
要素のドラッグ&ドロップ + 拡大/縮小
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=no;" /> <title>test</title> <style> div#target { position: absolute; height: 200px; width: 200px; background-color: blue; } </style> </head> <body> <h1>Gesture test.</h1> <div id="target"></div> <script> var target = document.getElementById('target'), styles = document.defaultView.getComputedStyle(target, ''), posX, posY, startX, startY, width = stripPx(styles.width), height = stripPx(styles.height), rotation = 0, dragging = false; function touchStart(ev) { if(ev.touches.length == 1) { var touch = ev.touches[0], styles = touch.style; startX = touch.pageX; startY = touch.pageY; posX = stripPx(target.style.left); posY = stripPx(target.style.top); dragging = true; } } function touchMove(ev) { if(ev.touches.length == 1) { if(dragging) { var touch = ev.touches[0], styles = target.style; styles.left = posX + (touch.pageX - startX) + 'px'; styles.top = posY + (touch.pageY - startY) + 'px'; } } } function touchEnd(ev) { dragging = false; } function gestureChange(ev) { target.style.width = (width * ev.scale) + 'px'; target.style.height = (height * ev.scale) + 'px'; target.style.webkitTransform = 'rotate(' + ((rotation + ev.rotation) % 360) + 'deg)'; } function gestureEnd(ev) { width *= ev.scale; height *= ev.scale; rotation = (rotation + ev.rotation) % 360; } function stripPx(v) { if(v == '') { return 0 }; return parseFloat(v.substring(0, v.length - 2)); } target.addEventListener('touchstart', touchStart, false); target.addEventListener('touchmove', touchMove, false); target.addEventListener('touchend', touchEnd, false); target.addEventListener('gesturechange', gestureChange, false); target.addEventListener('gestureend', gestureEnd, false); document.addEventListener('touchmove', function(ev) { ev.preventDefault(); }, false); </script> </body> </html>
【追記】
拡大、縮小は
target.style.webkitTransform = 'scale(' + ev.scale + ')';
の方が楽ですね。