Handler.js 9.7 KB

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