touch-pinch
Version:
minimal two-finger pinch gesture detection
154 lines (130 loc) • 4.11 kB
JavaScript
var getDistance = require('gl-vec2/distance')
var EventEmitter = require('events').EventEmitter
var dprop = require('dprop')
var eventOffset = require('mouse-event-offset')
module.exports = touchPinch
function touchPinch (target) {
target = target || window
var emitter = new EventEmitter()
var fingers = [ null, null ]
var activeCount = 0
var lastDistance = 0
var ended = false
var enabled = false
// some read-only values
Object.defineProperties(emitter, {
pinching: dprop(function () {
return activeCount === 2
}),
fingers: dprop(function () {
return fingers
})
})
enable()
emitter.enable = enable
emitter.disable = disable
emitter.indexOfTouch = indexOfTouch
return emitter
function indexOfTouch (touch) {
var id = touch.identifier
for (var i = 0; i < fingers.length; i++) {
if (fingers[i] &&
fingers[i].touch &&
fingers[i].touch.identifier === id) {
return i
}
}
return -1
}
function enable () {
if (enabled) return
enabled = true
target.addEventListener('touchstart', onTouchStart, false)
target.addEventListener('touchmove', onTouchMove, false)
target.addEventListener('touchend', onTouchRemoved, false)
target.addEventListener('touchcancel', onTouchRemoved, false)
}
function disable () {
if (!enabled) return
enabled = false
activeCount = 0
fingers[0] = null
fingers[1] = null
lastDistance = 0
ended = false
target.removeEventListener('touchstart', onTouchStart, false)
target.removeEventListener('touchmove', onTouchMove, false)
target.removeEventListener('touchend', onTouchRemoved, false)
target.removeEventListener('touchcancel', onTouchRemoved, false)
}
function onTouchStart (ev) {
for (var i = 0; i < ev.changedTouches.length; i++) {
var newTouch = ev.changedTouches[i]
var id = newTouch.identifier
var idx = indexOfTouch(id)
if (idx === -1 && activeCount < 2) {
var first = activeCount === 0
// newest and previous finger (previous may be undefined)
var newIndex = fingers[0] ? 1 : 0
var oldIndex = fingers[0] ? 0 : 1
var newFinger = new Finger()
// add to stack
fingers[newIndex] = newFinger
activeCount++
// update touch event & position
newFinger.touch = newTouch
eventOffset(newTouch, target, newFinger.position)
var oldTouch = fingers[oldIndex] ? fingers[oldIndex].touch : undefined
emitter.emit('place', newTouch, oldTouch)
if (!first) {
var initialDistance = computeDistance()
ended = false
emitter.emit('start', initialDistance)
lastDistance = initialDistance
}
}
}
}
function onTouchMove (ev) {
var changed = false
for (var i = 0; i < ev.changedTouches.length; i++) {
var movedTouch = ev.changedTouches[i]
var idx = indexOfTouch(movedTouch)
if (idx !== -1) {
changed = true
fingers[idx].touch = movedTouch // avoid caching touches
eventOffset(movedTouch, target, fingers[idx].position)
}
}
if (activeCount === 2 && changed) {
var currentDistance = computeDistance()
emitter.emit('change', currentDistance, lastDistance)
lastDistance = currentDistance
}
}
function onTouchRemoved (ev) {
for (var i = 0; i < ev.changedTouches.length; i++) {
var removed = ev.changedTouches[i]
var idx = indexOfTouch(removed)
if (idx !== -1) {
fingers[idx] = null
activeCount--
var otherIdx = idx === 0 ? 1 : 0
var otherTouch = fingers[otherIdx] ? fingers[otherIdx].touch : undefined
emitter.emit('lift', removed, otherTouch)
}
}
if (!ended && activeCount !== 2) {
ended = true
emitter.emit('end')
}
}
function computeDistance () {
if (activeCount < 2) return 0
return getDistance(fingers[0].position, fingers[1].position)
}
}
function Finger () {
this.position = [0, 0]
this.touch = null
}