OLD | NEW |
(Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 'use strict'; |
| 6 |
| 7 /** |
| 8 * A class that listens for touch events and produces events when these |
| 9 * touches form gestures (e.g. pinching). |
| 10 */ |
| 11 class GestureDetector { |
| 12 /** |
| 13 * Constructs a GestureDetector. |
| 14 * @param {!Element} element The element to monitor for touch gestures. |
| 15 */ |
| 16 constructor(element) { |
| 17 this.element_ = element; |
| 18 |
| 19 this.element_.addEventListener('touchstart', this.onTouchStart_.bind(this)); |
| 20 this.element_.addEventListener('touchmove', this.onTouch_.bind(this)); |
| 21 this.element_.addEventListener('touchend', this.onTouch_.bind(this)); |
| 22 this.element_.addEventListener('touchcancel', this.onTouch_.bind(this)); |
| 23 |
| 24 this.pinchStartEvent_ = null; |
| 25 this.lastEvent_ = null; |
| 26 |
| 27 this.listeners_ = new Map([ |
| 28 ['pinchstart', []], |
| 29 ['pinchupdate', []], |
| 30 ['pinchend', []] |
| 31 ]); |
| 32 } |
| 33 |
| 34 /** |
| 35 * Add a |listener| to be notified of |type| events. |
| 36 * @param {string} type The event type to be notified for. |
| 37 * @param {Function} listener The callback. |
| 38 */ |
| 39 addEventListener(type, listener) { |
| 40 if (this.listeners_.has(type)) { |
| 41 this.listeners_.get(type).push(listener); |
| 42 } |
| 43 } |
| 44 |
| 45 /** |
| 46 * Call the relevant listeners with the given |pinchEvent|. |
| 47 * @private |
| 48 * @param {!Object} pinchEvent The event to notify the listeners of. |
| 49 */ |
| 50 notify_(pinchEvent) { |
| 51 let listeners = this.listeners_.get(pinchEvent.type); |
| 52 |
| 53 for (let l of listeners) |
| 54 l(pinchEvent); |
| 55 } |
| 56 |
| 57 /** |
| 58 * The callback for touchstart events on the element. |
| 59 * @private |
| 60 * @param {!TouchEvent} event Touch event on the element. |
| 61 */ |
| 62 onTouchStart_(event) { |
| 63 // We must preventDefault if there is a two finger touch. By doing so |
| 64 // native pinch-zoom does not interfere with our way of handling the event. |
| 65 if (event.touches.length == 2) { |
| 66 event.preventDefault(); |
| 67 this.pinchStartEvent_ = event; |
| 68 this.lastEvent_ = event; |
| 69 this.notify_({ |
| 70 type: 'pinchstart', |
| 71 center: GestureDetector.center_(event) |
| 72 }); |
| 73 } |
| 74 } |
| 75 |
| 76 /** |
| 77 * The callback for touch move, end, and cancel events on the element. |
| 78 * @private |
| 79 * @param {!TouchEvent} event Touch event on the element. |
| 80 */ |
| 81 onTouch_(event) { |
| 82 if (!this.pinchStartEvent_) |
| 83 return; |
| 84 |
| 85 // Check if the pinch ends with the current event. |
| 86 if (event.touches.length < 2 || |
| 87 this.lastEvent_.touches.length !== event.touches.length) { |
| 88 let startScaleRatio = GestureDetector.pinchScaleRatio_( |
| 89 this.lastEvent_, this.pinchStartEvent_); |
| 90 let center = GestureDetector.center_(this.lastEvent_); |
| 91 let endEvent = { |
| 92 type: 'pinchend', |
| 93 startScaleRatio: startScaleRatio, |
| 94 center: center |
| 95 }; |
| 96 this.pinchStartEvent_ = null; |
| 97 this.lastEvent_ = null; |
| 98 this.notify_(endEvent); |
| 99 return; |
| 100 } |
| 101 |
| 102 let scaleRatio = GestureDetector.pinchScaleRatio_(event, this.lastEvent_); |
| 103 let startScaleRatio = GestureDetector.pinchScaleRatio_( |
| 104 event, this.pinchStartEvent_); |
| 105 let center = GestureDetector.center_(event); |
| 106 this.notify_({ |
| 107 type: 'pinchupdate', |
| 108 scaleRatio: scaleRatio, |
| 109 direction: scaleRatio > 1.0 ? 'in' : 'out', |
| 110 startScaleRatio: startScaleRatio, |
| 111 center: center |
| 112 }); |
| 113 |
| 114 this.lastEvent_ = event; |
| 115 } |
| 116 |
| 117 /** |
| 118 * Computes the change in scale between this touch event |
| 119 * and a previous one. |
| 120 * @private |
| 121 * @param {!TouchEvent} event Latest touch event on the element. |
| 122 * @param {!TouchEvent} prevEvent A previous touch event on the element. |
| 123 * @return {?number} The ratio of the scale of this event and the |
| 124 * scale of the previous one. |
| 125 */ |
| 126 static pinchScaleRatio_(event, prevEvent) { |
| 127 let distance1 = GestureDetector.distance_(prevEvent); |
| 128 let distance2 = GestureDetector.distance_(event); |
| 129 return distance1 === 0 ? null : distance2 / distance1; |
| 130 } |
| 131 |
| 132 /** |
| 133 * Computes the distance between fingers. |
| 134 * @private |
| 135 * @param {!TouchEvent} event Touch event with at least 2 touch points. |
| 136 * @return {number} Distance between touch[0] and touch[1]. |
| 137 */ |
| 138 static distance_(event) { |
| 139 let touch1 = event.touches[0]; |
| 140 let touch2 = event.touches[1]; |
| 141 let dx = touch1.clientX - touch2.clientX; |
| 142 let dy = touch1.clientY - touch2.clientY; |
| 143 return Math.sqrt(dx * dx + dy * dy); |
| 144 } |
| 145 |
| 146 /** |
| 147 * Computes the midpoint between fingers. |
| 148 * @private |
| 149 * @param {!TouchEvent} event Touch event with at least 2 touch points. |
| 150 * @return {!Object} Midpoint between touch[0] and touch[1]. |
| 151 */ |
| 152 static center_(event) { |
| 153 let touch1 = event.touches[0]; |
| 154 let touch2 = event.touches[1]; |
| 155 return { |
| 156 x: (touch1.clientX + touch2.clientX) / 2, |
| 157 y: (touch1.clientY + touch2.clientY) / 2 |
| 158 }; |
| 159 } |
| 160 }; |
OLD | NEW |