TouchEvents.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. L.Map.mergeOptions({
  2. touchExtend: true
  3. });
  4. /**
  5. * @class L.Map.TouchExtend
  6. * @aka TouchExtend
  7. */
  8. L.Map.TouchExtend = L.Handler.extend({
  9. // @method initialize(): void
  10. // Sets TouchExtend private accessor variables
  11. initialize: function (map) {
  12. this._map = map;
  13. this._container = map._container;
  14. this._pane = map._panes.overlayPane;
  15. },
  16. // @method addHooks(): void
  17. // Adds dom listener events to the map container
  18. addHooks: function () {
  19. L.DomEvent.on(this._container, 'touchstart', this._onTouchStart, this);
  20. L.DomEvent.on(this._container, 'touchend', this._onTouchEnd, this);
  21. L.DomEvent.on(this._container, 'touchmove', this._onTouchMove, this);
  22. if (this._detectIE()) {
  23. L.DomEvent.on(this._container, 'MSPointerDown', this._onTouchStart, this);
  24. L.DomEvent.on(this._container, 'MSPointerUp', this._onTouchEnd, this);
  25. L.DomEvent.on(this._container, 'MSPointerMove', this._onTouchMove, this);
  26. L.DomEvent.on(this._container, 'MSPointerCancel', this._onTouchCancel, this);
  27. } else {
  28. L.DomEvent.on(this._container, 'touchcancel', this._onTouchCancel, this);
  29. L.DomEvent.on(this._container, 'touchleave', this._onTouchLeave, this);
  30. }
  31. },
  32. // @method removeHooks(): void
  33. // Removes dom listener events from the map container
  34. removeHooks: function () {
  35. L.DomEvent.off(this._container, 'touchstart', this._onTouchStart);
  36. L.DomEvent.off(this._container, 'touchend', this._onTouchEnd);
  37. L.DomEvent.off(this._container, 'touchmove', this._onTouchMove);
  38. if (this._detectIE()) {
  39. L.DomEvent.off(this._container, 'MSPointerDowm', this._onTouchStart);
  40. L.DomEvent.off(this._container, 'MSPointerUp', this._onTouchEnd);
  41. L.DomEvent.off(this._container, 'MSPointerMove', this._onTouchMove);
  42. L.DomEvent.off(this._container, 'MSPointerCancel', this._onTouchCancel);
  43. } else {
  44. L.DomEvent.off(this._container, 'touchcancel', this._onTouchCancel);
  45. L.DomEvent.off(this._container, 'touchleave', this._onTouchLeave);
  46. }
  47. },
  48. _touchEvent: function (e, type) {
  49. // #TODO: fix the pageX error that is do a bug in Android where a single touch triggers two click events
  50. // _filterClick is what leaflet uses as a workaround.
  51. // This is a problem with more things than just android. Another problem is touchEnd has no touches in
  52. // its touch list.
  53. var touchEvent = {};
  54. if (typeof e.touches !== 'undefined') {
  55. if (!e.touches.length) {
  56. return;
  57. }
  58. touchEvent = e.touches[0];
  59. } else if (e.pointerType === 'touch') {
  60. touchEvent = e;
  61. if (!this._filterClick(e)) {
  62. return;
  63. }
  64. } else {
  65. return;
  66. }
  67. var containerPoint = this._map.mouseEventToContainerPoint(touchEvent),
  68. layerPoint = this._map.mouseEventToLayerPoint(touchEvent),
  69. latlng = this._map.layerPointToLatLng(layerPoint);
  70. this._map.fire(type, {
  71. latlng: latlng,
  72. layerPoint: layerPoint,
  73. containerPoint: containerPoint,
  74. pageX: touchEvent.pageX,
  75. pageY: touchEvent.pageY,
  76. originalEvent: e
  77. });
  78. },
  79. /** Borrowed from Leaflet and modified for bool ops **/
  80. _filterClick: function (e) {
  81. var timeStamp = (e.timeStamp || e.originalEvent.timeStamp),
  82. elapsed = L.DomEvent._lastClick && (timeStamp - L.DomEvent._lastClick);
  83. // are they closer together than 500ms yet more than 100ms?
  84. // Android typically triggers them ~300ms apart while multiple listeners
  85. // on the same event should be triggered far faster;
  86. // or check if click is simulated on the element, and if it is, reject any non-simulated events
  87. if ((elapsed && elapsed > 100 && elapsed < 500) || (e.target._simulatedClick && !e._simulated)) {
  88. L.DomEvent.stop(e);
  89. return false;
  90. }
  91. L.DomEvent._lastClick = timeStamp;
  92. return true;
  93. },
  94. _onTouchStart: function (e) {
  95. if (!this._map._loaded) {
  96. return;
  97. }
  98. var type = 'touchstart';
  99. this._touchEvent(e, type);
  100. },
  101. _onTouchEnd: function (e) {
  102. if (!this._map._loaded) {
  103. return;
  104. }
  105. var type = 'touchend';
  106. this._touchEvent(e, type);
  107. },
  108. _onTouchCancel: function (e) {
  109. if (!this._map._loaded) {
  110. return;
  111. }
  112. var type = 'touchcancel';
  113. if (this._detectIE()) {
  114. type = 'pointercancel';
  115. }
  116. this._touchEvent(e, type);
  117. },
  118. _onTouchLeave: function (e) {
  119. if (!this._map._loaded) {
  120. return;
  121. }
  122. var type = 'touchleave';
  123. this._touchEvent(e, type);
  124. },
  125. _onTouchMove: function (e) {
  126. if (!this._map._loaded) {
  127. return;
  128. }
  129. var type = 'touchmove';
  130. this._touchEvent(e, type);
  131. },
  132. _detectIE: function () {
  133. var ua = window.navigator.userAgent;
  134. var msie = ua.indexOf('MSIE ');
  135. if (msie > 0) {
  136. // IE 10 or older => return version number
  137. return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
  138. }
  139. var trident = ua.indexOf('Trident/');
  140. if (trident > 0) {
  141. // IE 11 => return version number
  142. var rv = ua.indexOf('rv:');
  143. return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
  144. }
  145. var edge = ua.indexOf('Edge/');
  146. if (edge > 0) {
  147. // IE 12 => return version number
  148. return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
  149. }
  150. // other browser
  151. return false;
  152. }
  153. });
  154. L.Map.addInitHook('addHandler', 'touchExtend', L.Map.TouchExtend);
  155. /**
  156. * @class L.Marker.Touch
  157. * @aka Marker.Touch
  158. *
  159. * This isn't full Touch support. This is just to get markers to also support dom touch events after creation
  160. * #TODO: find a better way of getting markers to support touch.
  161. */
  162. L.Marker.Touch = L.Marker.extend({
  163. _initInteraction: function () {
  164. if (!this.addInteractiveTarget) {
  165. // 0.7.x support
  166. return this._initInteractionLegacy();
  167. }
  168. // TODO this may need be updated to re-add touch events for 1.0+
  169. return L.Marker.prototype._initInteraction.apply(this);
  170. },
  171. // This is an exact copy of https://github.com/Leaflet/Leaflet/blob/v0.7/src/layer/marker/Marker.js
  172. // with the addition of the touch events
  173. _initInteractionLegacy: function () {
  174. if (!this.options.clickable) {
  175. return;
  176. }
  177. // TODO refactor into something shared with Map/Path/etc. to DRY it up
  178. var icon = this._icon,
  179. events = ['dblclick',
  180. 'mousedown',
  181. 'mouseover',
  182. 'mouseout',
  183. 'contextmenu',
  184. 'touchstart',
  185. 'touchend',
  186. 'touchmove'];
  187. if (this._detectIE) {
  188. events.concat(['MSPointerDown',
  189. 'MSPointerUp',
  190. 'MSPointerMove',
  191. 'MSPointerCancel']);
  192. } else {
  193. events.concat(['touchcancel']);
  194. }
  195. L.DomUtil.addClass(icon, 'leaflet-clickable');
  196. L.DomEvent.on(icon, 'click', this._onMouseClick, this);
  197. L.DomEvent.on(icon, 'keypress', this._onKeyPress, this);
  198. for (var i = 0; i < events.length; i++) {
  199. L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
  200. }
  201. if (L.Handler.MarkerDrag) {
  202. this.dragging = new L.Handler.MarkerDrag(this);
  203. if (this.options.draggable) {
  204. this.dragging.enable();
  205. }
  206. }
  207. },
  208. _detectIE: function () {
  209. var ua = window.navigator.userAgent;
  210. var msie = ua.indexOf('MSIE ');
  211. if (msie > 0) {
  212. // IE 10 or older => return version number
  213. return parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
  214. }
  215. var trident = ua.indexOf('Trident/');
  216. if (trident > 0) {
  217. // IE 11 => return version number
  218. var rv = ua.indexOf('rv:');
  219. return parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
  220. }
  221. var edge = ua.indexOf('Edge/');
  222. if (edge > 0) {
  223. // IE 12 => return version number
  224. return parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
  225. }
  226. // other browser
  227. return false;
  228. }
  229. });