SYNCERのロゴ
アイキャッチ画像

回転ジェスチャーを検出する方法

公開日: 2017/07/31 | 更新日:

タッチデバイスの回転ジェスチャーを検出し、それによって画像を回転させてみましょう。タッチした位置の取得方法、2点間の座標の計算など基本的な知識がある方を前提にしています。

サンプルコード

要点は回転を開始した時と、指を動かした時の角度の差を求めることです。

基本の座標

回転を開始した時の角度を基本の角度(baseAngle)とします。2017年8月現在、Androidだとtouchstartイベントで1ヶ所のタッチしか受け取れません。そのため、touchmoveイベントで設定しましょう。

JavaScript

// 基本の角度
var baseAngle = null ;

// 角度を取得する関数
 function getAngle ( event ) {
	event.preventDefault() ;

	var touches = event.changedTouches ;

	// 2本以上の指の場合だけ処理
	if ( touches.length > 1 ) {
		// 座標1 (1本目の指)
		var x1 = touches[0].pageX ;
		var y1 = touches[0].pageY ;

		// 座標2 (2本目の指)
		var x2 = touches[1].pageX ;
		var y2 = touches[1].pageY ;

		// 2つの座標の中点を原点(0,0)とする
		var x3 = ( x1 + x2 ) / 2 ;
		var y3 = ( y1 + y2 ) / 2 ;

		// 座標を再計算
		x1 -= x3 ;
		y1 -= y3 ;

		// 垂直線Aと座標1-座標2間の直線Bが原点で交わる時の角度を取得
		return Math.atan2( 1 - y1, 0 - x1 ) * ( 180 / Math.PI ) ;
	}

	return null ;
}

// touchmove
element.ontouchmove = function ( event ) {
	// 操作開始時の距離
	baseAngle = getAngle( event ) ;
}

変化後の角度

次に開始位置から2本指を動かした時、その角度を変化後の角度(movedAngle)とします。先ほどのtouchmoveイベント内を変更します。

element.ontouchmove = function ( event ) {
	if ( baseAngle !== null ) {
		// 変化後の角度
		var movedAngle = getAngle( event ) ;
	} else {
		// 基本の角度
		baseAngle = getAngle( event ) ;
	}
}

差の計算

基本の角度と変化後の角度の差を算出します。この差を、そのまま画像の角度に加算すれば操作の分だけ回転できるというわけです。1回転するのは大変なので、大体、算出した差の3倍くらいの角度を加算すると操作が快適になります。

var difference = ( movedAngle - baseAngle ) * 3 ;

デモ

2本指

前章で説明した要点を踏襲して作成したコードです。タッチデバイスを使って、枠内の黄色い部分で回転ジェスチャーをしてみて下さい。

// <div id="target1"></div>
var divElement = document.getElementById( "target1" ) ;

// <img id="target2">
var imgElement = document.getElementById( "target2" ) ;

// base
var startAngle = null ;
var baseImageAngle = null ;

// timeout id
var timeoutId ;

// touchmove
divElement.ontouchmove = function ( event ) {
	if ( IsScrolling ) return ;

	event.preventDefault() ;

	var touches = event.changedTouches ;

	if ( touches.length > 1 ) {
		var x1 = touches[0].pageX ;
		var y1 = touches[0].pageY ;

		var x2 = touches[1].pageX ;
		var y2 = touches[1].pageY ;

		var x3 = ( x1 + x2 ) / 2 ;
		var y3 = ( y1 + y2 ) / 2 ;

		x1 -= x3 ;
		y1 -= y3 ;

		var angle = Math.atan2( 1 - y1, 0 - x1 ) * ( 180 / Math.PI ) ;

		clearTimeout( timeoutId ) ;

		if ( startAngle !== null && baseImageAngle !== null ) {
			var difference = angle - startAngle ;
			difference *= 3 ;	// 実際の3倍の角度で動かす

			if ( difference ) {
				imgElement.style.transform = "rotateZ(" + ( (baseImageAngle + difference) % 360 ) + "deg)" ;
			}

			timeoutId = setTimeout( function () {
				startAngle = null ;
				baseImageAngle = null ;
			}, 100 ) ;

		} else {
			startAngle = angle ;

			var cSSStyleDeclaration = window.getComputedStyle( imgElement, null ) ;
			var transformValue = cSSStyleDeclaration.getPropertyValue( "transform" ) ;
			var matches = transformValue.match( /(matrix\()((.)+)(\))/ ) ;

			if ( matches ) {
  			var values = matches[2].split( "," ).map( parseFloat ) ;
  			baseImageAngle = Math.atan2( values[1], values[0] ) * ( 180 / Math.PI ) ;

				while ( baseImageAngle > 360 || 0 > baseImageAngle ) {
					baseImageAngle += 360 ;
					baseImageAngle %= 360 ;
				}

			} else {
				baseImageAngle = null ;
				imgElement.style.transform = "rotateZ(0deg)" ;

			}
		}
	}
}

1本指

1つの指の位置を画像の中央にあるものとすれば、1本指による回転ジェスチャーが実現できます。

// <div id="target1"></div>
var divElement = document.getElementById( "target1" ) ;

// <img id="target2">
var imgElement = document.getElementById( "target2" ) ;

// base
var startAngle = null ;
var baseImageAngle = null ;

// timeout id
var timeoutId ;

// touchmove
divElement.ontouchmove = function ( event ) {
	if ( IsScrolling ) return ;

	event.preventDefault() ;

	var touches = event.changedTouches ;

//	if ( touches.length > 1 ) {
		// 1本目の指
		var x1 = touches[0].pageX ;
		var y1 = touches[0].pageY ;

		// 2本目の指 (画像の中央の座標で固定)
		var clientRect = imgElement.getBoundingClientRect() ;
		var x2 = window.pageXOffset + clientRect.left + ( imgElement.clientWidth / 2 ) ;
		var y2 = window.pageYOffset + clientRect.top + ( imgElement.clientHeight / 2 ) ;

		var x3 = ( x1 + x2 ) / 2 ;
		var y3 = ( y1 + y2 ) / 2 ;

		x1 -= x3 ;
		y1 -= y3 ;

		var angle = Math.atan2( 1 - y1, 0 - x1 ) * ( 180 / Math.PI ) ;

		clearTimeout( timeoutId ) ;

		if ( startAngle !== null && baseImageAngle !== null ) {
			var difference = angle - startAngle ;
			difference *= 3 ;	// 実際の3倍の角度で動かす

			if ( difference ) {
				imgElement.style.transform = "rotateZ(" + ( (baseImageAngle + difference) % 360 ) + "deg)" ;
			}

			timeoutId = setTimeout( function () {
				startAngle = null ;
				baseImageAngle = null ;
			}, 100 ) ;

		} else {
			startAngle = angle ;

			var cSSStyleDeclaration = window.getComputedStyle( imgElement, null ) ;
			var transformValue = cSSStyleDeclaration.getPropertyValue( "transform" ) ;
			var matches = transformValue.match( /(matrix\()((.)+)(\))/ ) ;

			if ( matches ) {
  			var values = matches[2].split( "," ).map( parseFloat ) ;
  			baseImageAngle = Math.atan2( values[1], values[0] ) * ( 180 / Math.PI ) ;

				while ( baseImageAngle > 360 || 0 > baseImageAngle ) {
					baseImageAngle += 360 ;
					baseImageAngle %= 360 ;
				}

			} else {
				baseImageAngle = null ;
				imgElement.style.transform = "rotateZ(0deg)" ;

			}
		}
//	}
}
  • Twitterでシェア
  • Facebookでシェア
  • Google+でシェア
  • はてなブックマークでシェア
  • pocketに保存
  • LINEでシェア
更新履歴
2017年8月1日 (火)
一部コードの不備を修正し、回転の不具合を解消しました。1本指で回転ジェスチャーを実装する方法を追記しました。
2017年7月31日 (月)
コンテンツを公開しました。