const MAXIMUM_TIME_FOR_RECORDING_GESTURES = 100; const MAXIMUM_DECELERATION_TIME = 500; class PinchGestureRecognizer extends GestureRecognizer { constructor(target, delegate) { super(target, delegate); this.scaleThreshold = 0; this._scaledMinimumAmount = false; } // Public get velocity() { const lastGesture = this._gestures[this._gestures.length - 1]; if (!lastGesture) return this._velocity; const elapsedTime = Date.now() - (lastGesture.timeStamp + MAXIMUM_TIME_FOR_RECORDING_GESTURES); if (elapsedTime <= 0) return this._velocity; const f = Math.max((MAXIMUM_DECELERATION_TIME - elapsedTime) / MAXIMUM_DECELERATION_TIME, 0); return this._velocity * f; } // Protected touchesBegan(event) { if (event.currentTarget !== this.target) return; // Additional setup for when the the platform doesn't natively // provide us with gesture events. if (!GestureRecognizer.SupportsGestures) { // A pinch gesture can only be performed with 2 fingers, anything more // and we failed our gesture. if (this.numberOfTouches > 2) { this.enterFailedState(); return; } // We can only start tracking touches with 2 fingers. if (this.numberOfTouches !== 2) return; this._startDistance = this._distance(); // We manually add a start value so that we always have 2 entries in the // _gestures array so that we don't have to check for the existence of 2 // entries when computing velocity. this._recordGesture(1); this._scaledMinimumAmount = false; this._updateStateWithEvent(event); } else if (this.numberOfTouches !== 2) { // When we support gesture events, we only care about the case where we're // using two fingers. return; } super.touchesBegan(event); } touchesMoved(event) { // This method only needs to be overriden in the case where the platform // doesn't natively provide us with gesture events. if (GestureRecognizer.SupportsGestures) return; if (this.numberOfTouches !== 2) return; this._updateStateWithEvent(event); } touchesEnded(event) { // This method only needs to be overriden in the case where the platform // doesn't natively provide us with gesture events. if (GestureRecognizer.SupportsGestures) return; // If we don't have the required number of touches or have not event // obtained 2 fingers, then there's nothing for us to do. if (this.numberOfTouches >= 2 || !this._startDistance) return; if (this._scaledMinimumAmount) this.enterEndedState(); else this.enterFailedState(); } gestureBegan(event) { super.gestureBegan(event); // We manually add a start value so that we always have 2 entries in the // _gestures array so that we don't have to check for the existence of 2 // entries when computing velocity. this._recordGesture(event.scale); this._scaledMinimumAmount = false; this._updateStateWithEvent(event); event.preventDefault(); } gestureChanged(event) { event.preventDefault(); this._updateStateWithEvent(event); } gestureEnded(event) { if (this._scaledMinimumAmount) this.enterEndedState(); else this.enterFailedState(); } reset() { this.scale = 1; this._velocity = 0; this._gestures = []; delete this._startDistance; } // Private _recordGesture(scale) { const currentTime = Date.now(); const count = this._gestures.push({ scale: scale, timeStamp: currentTime }); // We want to keep at least two gestures at all times. if (count <= 2) return; const scaleDirection = this._gestures[count - 1].scale >= this._gestures[count - 2].scale; let i = count - 3; for (; i >= 0; --i) { let gesture = this._gestures[i]; if (currentTime - gesture.timeStamp > MAXIMUM_TIME_FOR_RECORDING_GESTURES || this._gestures[i + 1].scale >= gesture.scale !== scaleDirection) break; } if (i > 0) this._gestures = this._gestures.slice(i + 1); } _updateStateWithEvent(event) { const scaleSinceStart = GestureRecognizer.SupportsGestures ? event.scale : this._distance() / this._startDistance; if (!this._scaledMinimumAmount) { if (Math.abs(1 - scaleSinceStart) >= this.scaleThreshold) { this._scaledMinimumAmount = true; this.scale = 1; this.enterBeganState(); } return; } this._recordGesture(scaleSinceStart); const oldestGesture = this._gestures[0]; const ds = scaleSinceStart - oldestGesture.scale; const dt = Date.now() - oldestGesture.timeStamp; this._velocity = (dt === 0) ? 0 : ds / dt * 1000; this.scale *= scaleSinceStart / this._gestures[this._gestures.length - 2].scale; this.enterChangedState(); } _distance() { console.assert(this.numberOfTouches === 2); const firstTouch = this._targetTouches[0]; const firstTouchPoint = new DOMPoint(firstTouch.pageX, firstTouch.pageY); const secondTouch = this._targetTouches[1]; const secondTouchPoint = new DOMPoint(secondTouch.pageX, secondTouch.pageY); return Math.sqrt(Math.pow(firstTouchPoint.x - secondTouchPoint.x, 2) + Math.pow(firstTouchPoint.y - secondTouchPoint.y, 2)); } }