HandlerProxy.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. var _event = require("../core/event");
  2. var addEventListener = _event.addEventListener;
  3. var removeEventListener = _event.removeEventListener;
  4. var normalizeEvent = _event.normalizeEvent;
  5. var zrUtil = require("../core/util");
  6. var Eventful = require("../mixin/Eventful");
  7. var env = require("../core/env");
  8. var GestureMgr = require("../core/GestureMgr");
  9. var TOUCH_CLICK_DELAY = 300;
  10. var mouseHandlerNames = ['click', 'dblclick', 'mousewheel', 'mouseout', 'mouseup', 'mousedown', 'mousemove', 'contextmenu'];
  11. var touchHandlerNames = ['touchstart', 'touchend', 'touchmove'];
  12. var pointerEventNames = {
  13. pointerdown: 1,
  14. pointerup: 1,
  15. pointermove: 1,
  16. pointerout: 1
  17. };
  18. var pointerHandlerNames = zrUtil.map(mouseHandlerNames, function (name) {
  19. var nm = name.replace('mouse', 'pointer');
  20. return pointerEventNames[nm] ? nm : name;
  21. });
  22. function eventNameFix(name) {
  23. return name === 'mousewheel' && env.browser.firefox ? 'DOMMouseScroll' : name;
  24. }
  25. function processGesture(proxy, event, stage) {
  26. var gestureMgr = proxy._gestureMgr;
  27. stage === 'start' && gestureMgr.clear();
  28. var gestureInfo = gestureMgr.recognize(event, proxy.handler.findHover(event.zrX, event.zrY, null).target, proxy.dom);
  29. stage === 'end' && gestureMgr.clear(); // Do not do any preventDefault here. Upper application do that if necessary.
  30. if (gestureInfo) {
  31. var type = gestureInfo.type;
  32. event.gestureEvent = type;
  33. proxy.handler.dispatchToElement({
  34. target: gestureInfo.target
  35. }, type, gestureInfo.event);
  36. }
  37. } // function onMSGestureChange(proxy, event) {
  38. // if (event.translationX || event.translationY) {
  39. // // mousemove is carried by MSGesture to reduce the sensitivity.
  40. // proxy.handler.dispatchToElement(event.target, 'mousemove', event);
  41. // }
  42. // if (event.scale !== 1) {
  43. // event.pinchX = event.offsetX;
  44. // event.pinchY = event.offsetY;
  45. // event.pinchScale = event.scale;
  46. // proxy.handler.dispatchToElement(event.target, 'pinch', event);
  47. // }
  48. // }
  49. /**
  50. * Prevent mouse event from being dispatched after Touch Events action
  51. * @see <https://github.com/deltakosh/handjs/blob/master/src/hand.base.js>
  52. * 1. Mobile browsers dispatch mouse events 300ms after touchend.
  53. * 2. Chrome for Android dispatch mousedown for long-touch about 650ms
  54. * Result: Blocking Mouse Events for 700ms.
  55. */
  56. function setTouchTimer(instance) {
  57. instance._touching = true;
  58. clearTimeout(instance._touchTimer);
  59. instance._touchTimer = setTimeout(function () {
  60. instance._touching = false;
  61. }, 700);
  62. }
  63. var domHandlers = {
  64. /**
  65. * Mouse move handler
  66. * @inner
  67. * @param {Event} event
  68. */
  69. mousemove: function (event) {
  70. event = normalizeEvent(this.dom, event);
  71. this.trigger('mousemove', event);
  72. },
  73. /**
  74. * Mouse out handler
  75. * @inner
  76. * @param {Event} event
  77. */
  78. mouseout: function (event) {
  79. event = normalizeEvent(this.dom, event);
  80. var element = event.toElement || event.relatedTarget;
  81. if (element != this.dom) {
  82. while (element && element.nodeType != 9) {
  83. // 忽略包含在root中的dom引起的mouseOut
  84. if (element === this.dom) {
  85. return;
  86. }
  87. element = element.parentNode;
  88. }
  89. }
  90. this.trigger('mouseout', event);
  91. },
  92. /**
  93. * Touch开始响应函数
  94. * @inner
  95. * @param {Event} event
  96. */
  97. touchstart: function (event) {
  98. // Default mouse behaviour should not be disabled here.
  99. // For example, page may needs to be slided.
  100. event = normalizeEvent(this.dom, event); // Mark touch, which is useful in distinguish touch and
  101. // mouse event in upper applicatoin.
  102. event.zrByTouch = true;
  103. this._lastTouchMoment = new Date();
  104. processGesture(this, event, 'start'); // In touch device, trigger `mousemove`(`mouseover`) should
  105. // be triggered, and must before `mousedown` triggered.
  106. domHandlers.mousemove.call(this, event);
  107. domHandlers.mousedown.call(this, event);
  108. setTouchTimer(this);
  109. },
  110. /**
  111. * Touch移动响应函数
  112. * @inner
  113. * @param {Event} event
  114. */
  115. touchmove: function (event) {
  116. event = normalizeEvent(this.dom, event); // Mark touch, which is useful in distinguish touch and
  117. // mouse event in upper applicatoin.
  118. event.zrByTouch = true;
  119. processGesture(this, event, 'change'); // Mouse move should always be triggered no matter whether
  120. // there is gestrue event, because mouse move and pinch may
  121. // be used at the same time.
  122. domHandlers.mousemove.call(this, event);
  123. setTouchTimer(this);
  124. },
  125. /**
  126. * Touch结束响应函数
  127. * @inner
  128. * @param {Event} event
  129. */
  130. touchend: function (event) {
  131. event = normalizeEvent(this.dom, event); // Mark touch, which is useful in distinguish touch and
  132. // mouse event in upper applicatoin.
  133. event.zrByTouch = true;
  134. processGesture(this, event, 'end');
  135. domHandlers.mouseup.call(this, event); // Do not trigger `mouseout` here, in spite of `mousemove`(`mouseover`) is
  136. // triggered in `touchstart`. This seems to be illogical, but by this mechanism,
  137. // we can conveniently implement "hover style" in both PC and touch device just
  138. // by listening to `mouseover` to add "hover style" and listening to `mouseout`
  139. // to remove "hover style" on an element, without any additional code for
  140. // compatibility. (`mouseout` will not be triggered in `touchend`, so "hover
  141. // style" will remain for user view)
  142. // click event should always be triggered no matter whether
  143. // there is gestrue event. System click can not be prevented.
  144. if (+new Date() - this._lastTouchMoment < TOUCH_CLICK_DELAY) {
  145. domHandlers.click.call(this, event);
  146. }
  147. setTouchTimer(this);
  148. },
  149. pointerdown: function (event) {
  150. domHandlers.mousedown.call(this, event); // if (useMSGuesture(this, event)) {
  151. // this._msGesture.addPointer(event.pointerId);
  152. // }
  153. },
  154. pointermove: function (event) {
  155. // FIXME
  156. // pointermove is so sensitive that it always triggered when
  157. // tap(click) on touch screen, which affect some judgement in
  158. // upper application. So, we dont support mousemove on MS touch
  159. // device yet.
  160. if (!isPointerFromTouch(event)) {
  161. domHandlers.mousemove.call(this, event);
  162. }
  163. },
  164. pointerup: function (event) {
  165. domHandlers.mouseup.call(this, event);
  166. },
  167. pointerout: function (event) {
  168. // pointerout will be triggered when tap on touch screen
  169. // (IE11+/Edge on MS Surface) after click event triggered,
  170. // which is inconsistent with the mousout behavior we defined
  171. // in touchend. So we unify them.
  172. // (check domHandlers.touchend for detailed explanation)
  173. if (!isPointerFromTouch(event)) {
  174. domHandlers.mouseout.call(this, event);
  175. }
  176. }
  177. };
  178. function isPointerFromTouch(event) {
  179. var pointerType = event.pointerType;
  180. return pointerType === 'pen' || pointerType === 'touch';
  181. } // function useMSGuesture(handlerProxy, event) {
  182. // return isPointerFromTouch(event) && !!handlerProxy._msGesture;
  183. // }
  184. // Common handlers
  185. zrUtil.each(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
  186. domHandlers[name] = function (event) {
  187. event = normalizeEvent(this.dom, event);
  188. this.trigger(name, event);
  189. };
  190. });
  191. /**
  192. * 为控制类实例初始化dom 事件处理函数
  193. *
  194. * @inner
  195. * @param {module:zrender/Handler} instance 控制类实例
  196. */
  197. function initDomHandler(instance) {
  198. zrUtil.each(touchHandlerNames, function (name) {
  199. instance._handlers[name] = zrUtil.bind(domHandlers[name], instance);
  200. });
  201. zrUtil.each(pointerHandlerNames, function (name) {
  202. instance._handlers[name] = zrUtil.bind(domHandlers[name], instance);
  203. });
  204. zrUtil.each(mouseHandlerNames, function (name) {
  205. instance._handlers[name] = makeMouseHandler(domHandlers[name], instance);
  206. });
  207. function makeMouseHandler(fn, instance) {
  208. return function () {
  209. if (instance._touching) {
  210. return;
  211. }
  212. return fn.apply(instance, arguments);
  213. };
  214. }
  215. }
  216. function HandlerDomProxy(dom) {
  217. Eventful.call(this);
  218. this.dom = dom;
  219. /**
  220. * @private
  221. * @type {boolean}
  222. */
  223. this._touching = false;
  224. /**
  225. * @private
  226. * @type {number}
  227. */
  228. this._touchTimer;
  229. /**
  230. * @private
  231. * @type {module:zrender/core/GestureMgr}
  232. */
  233. this._gestureMgr = new GestureMgr();
  234. this._handlers = {};
  235. initDomHandler(this);
  236. if (env.pointerEventsSupported) {
  237. // Only IE11+/Edge
  238. // 1. On devices that both enable touch and mouse (e.g., MS Surface and lenovo X240),
  239. // IE11+/Edge do not trigger touch event, but trigger pointer event and mouse event
  240. // at the same time.
  241. // 2. On MS Surface, it probablely only trigger mousedown but no mouseup when tap on
  242. // screen, which do not occurs in pointer event.
  243. // So we use pointer event to both detect touch gesture and mouse behavior.
  244. mountHandlers(pointerHandlerNames, this); // FIXME
  245. // Note: MS Gesture require CSS touch-action set. But touch-action is not reliable,
  246. // which does not prevent defuault behavior occasionally (which may cause view port
  247. // zoomed in but use can not zoom it back). And event.preventDefault() does not work.
  248. // So we have to not to use MSGesture and not to support touchmove and pinch on MS
  249. // touch screen. And we only support click behavior on MS touch screen now.
  250. // MS Gesture Event is only supported on IE11+/Edge and on Windows 8+.
  251. // We dont support touch on IE on win7.
  252. // See <https://msdn.microsoft.com/en-us/library/dn433243(v=vs.85).aspx>
  253. // if (typeof MSGesture === 'function') {
  254. // (this._msGesture = new MSGesture()).target = dom; // jshint ignore:line
  255. // dom.addEventListener('MSGestureChange', onMSGestureChange);
  256. // }
  257. } else {
  258. if (env.touchEventsSupported) {
  259. mountHandlers(touchHandlerNames, this); // Handler of 'mouseout' event is needed in touch mode, which will be mounted below.
  260. // addEventListener(root, 'mouseout', this._mouseoutHandler);
  261. } // 1. Considering some devices that both enable touch and mouse event (like on MS Surface
  262. // and lenovo X240, @see #2350), we make mouse event be always listened, otherwise
  263. // mouse event can not be handle in those devices.
  264. // 2. On MS Surface, Chrome will trigger both touch event and mouse event. How to prevent
  265. // mouseevent after touch event triggered, see `setTouchTimer`.
  266. mountHandlers(mouseHandlerNames, this);
  267. }
  268. function mountHandlers(handlerNames, instance) {
  269. zrUtil.each(handlerNames, function (name) {
  270. addEventListener(dom, eventNameFix(name), instance._handlers[name]);
  271. }, instance);
  272. }
  273. }
  274. var handlerDomProxyProto = HandlerDomProxy.prototype;
  275. handlerDomProxyProto.dispose = function () {
  276. var handlerNames = mouseHandlerNames.concat(touchHandlerNames);
  277. for (var i = 0; i < handlerNames.length; i++) {
  278. var name = handlerNames[i];
  279. removeEventListener(this.dom, eventNameFix(name), this._handlers[name]);
  280. }
  281. };
  282. handlerDomProxyProto.setCursor = function (cursorStyle) {
  283. this.dom.style.cursor = cursorStyle || 'default';
  284. };
  285. zrUtil.mixin(HandlerDomProxy, Eventful);
  286. var _default = HandlerDomProxy;
  287. module.exports = _default;