Handler.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. var util = require("./core/util");
  2. var vec2 = require("./core/vector");
  3. var Draggable = require("./mixin/Draggable");
  4. var Eventful = require("./mixin/Eventful");
  5. /**
  6. * Handler
  7. * @module zrender/Handler
  8. * @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
  9. * errorrik (errorrik@gmail.com)
  10. * pissang (shenyi.914@gmail.com)
  11. */
  12. var SILENT = 'silent';
  13. function makeEventPacket(eveType, targetInfo, event) {
  14. return {
  15. type: eveType,
  16. event: event,
  17. // target can only be an element that is not silent.
  18. target: targetInfo.target,
  19. // topTarget can be a silent element.
  20. topTarget: targetInfo.topTarget,
  21. cancelBubble: false,
  22. offsetX: event.zrX,
  23. offsetY: event.zrY,
  24. gestureEvent: event.gestureEvent,
  25. pinchX: event.pinchX,
  26. pinchY: event.pinchY,
  27. pinchScale: event.pinchScale,
  28. wheelDelta: event.zrDelta,
  29. zrByTouch: event.zrByTouch,
  30. which: event.which
  31. };
  32. }
  33. function EmptyProxy() {}
  34. EmptyProxy.prototype.dispose = function () {};
  35. var handlerNames = ['click', 'dblclick', 'mousewheel', 'mouseout', 'mouseup', 'mousedown', 'mousemove', 'contextmenu'];
  36. /**
  37. * @alias module:zrender/Handler
  38. * @constructor
  39. * @extends module:zrender/mixin/Eventful
  40. * @param {module:zrender/Storage} storage Storage instance.
  41. * @param {module:zrender/Painter} painter Painter instance.
  42. * @param {module:zrender/dom/HandlerProxy} proxy HandlerProxy instance.
  43. * @param {HTMLElement} painterRoot painter.root (not painter.getViewportRoot()).
  44. */
  45. var Handler = function (storage, painter, proxy, painterRoot) {
  46. Eventful.call(this);
  47. this.storage = storage;
  48. this.painter = painter;
  49. this.painterRoot = painterRoot;
  50. proxy = proxy || new EmptyProxy();
  51. /**
  52. * Proxy of event. can be Dom, WebGLSurface, etc.
  53. */
  54. this.proxy = proxy; // Attach handler
  55. proxy.handler = this;
  56. /**
  57. * {target, topTarget, x, y}
  58. * @private
  59. * @type {Object}
  60. */
  61. this._hovered = {};
  62. /**
  63. * @private
  64. * @type {Date}
  65. */
  66. this._lastTouchMoment;
  67. /**
  68. * @private
  69. * @type {number}
  70. */
  71. this._lastX;
  72. /**
  73. * @private
  74. * @type {number}
  75. */
  76. this._lastY;
  77. Draggable.call(this);
  78. util.each(handlerNames, function (name) {
  79. proxy.on && proxy.on(name, this[name], this);
  80. }, this);
  81. };
  82. Handler.prototype = {
  83. constructor: Handler,
  84. mousemove: function (event) {
  85. var x = event.zrX;
  86. var y = event.zrY;
  87. var lastHovered = this._hovered;
  88. var lastHoveredTarget = lastHovered.target; // If lastHoveredTarget is removed from zr (detected by '__zr') by some API call
  89. // (like 'setOption' or 'dispatchAction') in event handlers, we should find
  90. // lastHovered again here. Otherwise 'mouseout' can not be triggered normally.
  91. // See #6198.
  92. if (lastHoveredTarget && !lastHoveredTarget.__zr) {
  93. lastHovered = this.findHover(lastHovered.x, lastHovered.y);
  94. lastHoveredTarget = lastHovered.target;
  95. }
  96. var hovered = this._hovered = this.findHover(x, y);
  97. var hoveredTarget = hovered.target;
  98. var proxy = this.proxy;
  99. proxy.setCursor && proxy.setCursor(hoveredTarget ? hoveredTarget.cursor : 'default'); // Mouse out on previous hovered element
  100. if (lastHoveredTarget && hoveredTarget !== lastHoveredTarget) {
  101. this.dispatchToElement(lastHovered, 'mouseout', event);
  102. } // Mouse moving on one element
  103. this.dispatchToElement(hovered, 'mousemove', event); // Mouse over on a new element
  104. if (hoveredTarget && hoveredTarget !== lastHoveredTarget) {
  105. this.dispatchToElement(hovered, 'mouseover', event);
  106. }
  107. },
  108. mouseout: function (event) {
  109. this.dispatchToElement(this._hovered, 'mouseout', event); // There might be some doms created by upper layer application
  110. // at the same level of painter.getViewportRoot() (e.g., tooltip
  111. // dom created by echarts), where 'globalout' event should not
  112. // be triggered when mouse enters these doms. (But 'mouseout'
  113. // should be triggered at the original hovered element as usual).
  114. var element = event.toElement || event.relatedTarget;
  115. var innerDom;
  116. do {
  117. element = element && element.parentNode;
  118. } while (element && element.nodeType != 9 && !(innerDom = element === this.painterRoot));
  119. !innerDom && this.trigger('globalout', {
  120. event: event
  121. });
  122. },
  123. /**
  124. * Resize
  125. */
  126. resize: function (event) {
  127. this._hovered = {};
  128. },
  129. /**
  130. * Dispatch event
  131. * @param {string} eventName
  132. * @param {event=} eventArgs
  133. */
  134. dispatch: function (eventName, eventArgs) {
  135. var handler = this[eventName];
  136. handler && handler.call(this, eventArgs);
  137. },
  138. /**
  139. * Dispose
  140. */
  141. dispose: function () {
  142. this.proxy.dispose();
  143. this.storage = this.proxy = this.painter = null;
  144. },
  145. /**
  146. * 设置默认的cursor style
  147. * @param {string} [cursorStyle='default'] 例如 crosshair
  148. */
  149. setCursorStyle: function (cursorStyle) {
  150. var proxy = this.proxy;
  151. proxy.setCursor && proxy.setCursor(cursorStyle);
  152. },
  153. /**
  154. * 事件分发代理
  155. *
  156. * @private
  157. * @param {Object} targetInfo {target, topTarget} 目标图形元素
  158. * @param {string} eventName 事件名称
  159. * @param {Object} event 事件对象
  160. */
  161. dispatchToElement: function (targetInfo, eventName, event) {
  162. targetInfo = targetInfo || {};
  163. var el = targetInfo.target;
  164. if (el && el.silent) {
  165. return;
  166. }
  167. var eventHandler = 'on' + eventName;
  168. var eventPacket = makeEventPacket(eventName, targetInfo, event);
  169. while (el) {
  170. el[eventHandler] && (eventPacket.cancelBubble = el[eventHandler].call(el, eventPacket));
  171. el.trigger(eventName, eventPacket);
  172. el = el.parent;
  173. if (eventPacket.cancelBubble) {
  174. break;
  175. }
  176. }
  177. if (!eventPacket.cancelBubble) {
  178. // 冒泡到顶级 zrender 对象
  179. this.trigger(eventName, eventPacket); // 分发事件到用户自定义层
  180. // 用户有可能在全局 click 事件中 dispose,所以需要判断下 painter 是否存在
  181. this.painter && this.painter.eachOtherLayer(function (layer) {
  182. if (typeof layer[eventHandler] == 'function') {
  183. layer[eventHandler].call(layer, eventPacket);
  184. }
  185. if (layer.trigger) {
  186. layer.trigger(eventName, eventPacket);
  187. }
  188. });
  189. }
  190. },
  191. /**
  192. * @private
  193. * @param {number} x
  194. * @param {number} y
  195. * @param {module:zrender/graphic/Displayable} exclude
  196. * @return {model:zrender/Element}
  197. * @method
  198. */
  199. findHover: function (x, y, exclude) {
  200. var list = this.storage.getDisplayList();
  201. var out = {
  202. x: x,
  203. y: y
  204. };
  205. for (var i = list.length - 1; i >= 0; i--) {
  206. var hoverCheckResult;
  207. if (list[i] !== exclude // getDisplayList may include ignored item in VML mode
  208. && !list[i].ignore && (hoverCheckResult = isHover(list[i], x, y))) {
  209. !out.topTarget && (out.topTarget = list[i]);
  210. if (hoverCheckResult !== SILENT) {
  211. out.target = list[i];
  212. break;
  213. }
  214. }
  215. }
  216. return out;
  217. }
  218. }; // Common handlers
  219. util.each(['click', 'mousedown', 'mouseup', 'mousewheel', 'dblclick', 'contextmenu'], function (name) {
  220. Handler.prototype[name] = function (event) {
  221. // Find hover again to avoid click event is dispatched manually. Or click is triggered without mouseover
  222. var hovered = this.findHover(event.zrX, event.zrY);
  223. var hoveredTarget = hovered.target;
  224. if (name === 'mousedown') {
  225. this._downEl = hoveredTarget;
  226. this._downPoint = [event.zrX, event.zrY]; // In case click triggered before mouseup
  227. this._upEl = hoveredTarget;
  228. } else if (name === 'mosueup') {
  229. this._upEl = hoveredTarget;
  230. } else if (name === 'click') {
  231. if (this._downEl !== this._upEl // Original click event is triggered on the whole canvas element,
  232. // including the case that `mousedown` - `mousemove` - `mouseup`,
  233. // which should be filtered, otherwise it will bring trouble to
  234. // pan and zoom.
  235. || !this._downPoint // Arbitrary value
  236. || vec2.dist(this._downPoint, [event.zrX, event.zrY]) > 4) {
  237. return;
  238. }
  239. this._downPoint = null;
  240. }
  241. this.dispatchToElement(hovered, name, event);
  242. };
  243. });
  244. function isHover(displayable, x, y) {
  245. if (displayable[displayable.rectHover ? 'rectContain' : 'contain'](x, y)) {
  246. var el = displayable;
  247. var isSilent;
  248. while (el) {
  249. // If clipped by ancestor.
  250. // FIXME: If clipPath has neither stroke nor fill,
  251. // el.clipPath.contain(x, y) will always return false.
  252. if (el.clipPath && !el.clipPath.contain(x, y)) {
  253. return false;
  254. }
  255. if (el.silent) {
  256. isSilent = true;
  257. }
  258. el = el.parent;
  259. }
  260. return isSilent ? SILENT : true;
  261. }
  262. return false;
  263. }
  264. util.mixin(Handler, Eventful);
  265. util.mixin(Handler, Draggable);
  266. var _default = Handler;
  267. module.exports = _default;