event-contoller.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. /**
  4. * @fileoverview 事件处理器
  5. * @author dxq613@gmail.com
  6. */
  7. var graph_event_1 = require("./graph-event");
  8. var util_1 = require("../util/util");
  9. var CLICK_OFFSET = 40;
  10. var LEFT_BTN_CODE = 0;
  11. var DELEGATION_SPLIT = ':';
  12. var EVENTS = [
  13. 'mousedown',
  14. 'mouseup',
  15. 'dblclick',
  16. 'mouseout',
  17. 'mouseover',
  18. 'mousemove',
  19. 'mouseleave',
  20. 'mouseenter',
  21. 'touchstart',
  22. 'touchmove',
  23. 'touchend',
  24. 'dragenter',
  25. 'dragover',
  26. 'dragleave',
  27. 'drop',
  28. 'contextmenu',
  29. 'mousewheel',
  30. ];
  31. // 是否有委托事件监听
  32. function hasDelegation(events, type) {
  33. for (var key in events) {
  34. if (events.hasOwnProperty(key) && key.indexOf(DELEGATION_SPLIT + type) >= 0) {
  35. return true;
  36. }
  37. }
  38. return false;
  39. }
  40. // 触发目标事件,目标只能是 shape 或 canvas
  41. function emitTargetEvent(target, type, eventObj) {
  42. eventObj.name = type;
  43. eventObj.target = target;
  44. eventObj.currentTarget = target;
  45. eventObj.delegateTarget = target;
  46. target.emit(type, eventObj);
  47. }
  48. // 事件冒泡, enter 和 leave 需要对 fromShape 和 toShape 进行判同
  49. function bubbleEvent(container, type, eventObj) {
  50. if (eventObj.bubbles) {
  51. var relativeShape = void 0;
  52. var isOverEvent = false;
  53. if (type === 'mouseenter') {
  54. relativeShape = eventObj.fromShape;
  55. isOverEvent = true;
  56. }
  57. else if (type === 'mouseleave') {
  58. isOverEvent = true;
  59. relativeShape = eventObj.toShape;
  60. }
  61. // canvas 上的 mouseenter, mouseleave 事件,仅当进入或者移出 canvas 时触发
  62. if (container.isCanvas() && isOverEvent) {
  63. return;
  64. }
  65. // 如果相关图形同当前图形在同一个容器内,不触发事件
  66. if (relativeShape && util_1.isParent(container, relativeShape)) {
  67. // 阻止继续向上冒泡
  68. eventObj.bubbles = false;
  69. return;
  70. }
  71. // 事件名称可能在委托过程中被修改,因此事件冒泡时需要重新设置事件名称
  72. eventObj.name = type;
  73. eventObj.currentTarget = container;
  74. eventObj.delegateTarget = container;
  75. container.emit(type, eventObj);
  76. }
  77. }
  78. var EventController = /** @class */ (function () {
  79. function EventController(cfg) {
  80. var _this = this;
  81. // 正在被拖拽的图形
  82. this.draggingShape = null;
  83. this.dragging = false;
  84. // 当前鼠标/touch所在位置的图形
  85. this.currentShape = null;
  86. this.mousedownShape = null;
  87. this.mousedownPoint = null;
  88. // 统一处理所有的回调
  89. this._eventCallback = function (ev) {
  90. var type = ev.type;
  91. _this._triggerEvent(type, ev);
  92. };
  93. // 在 document 处理拖拽到画布外的事件,处理从图形上移除画布未被捕捉的问题
  94. this._onDocumentMove = function (ev) {
  95. var canvas = _this.canvas;
  96. var el = canvas.get('el');
  97. if (el !== ev.target) {
  98. // 不在 canvas 上移动
  99. if (_this.dragging || _this.currentShape) {
  100. var pointInfo = _this._getPointInfo(ev);
  101. // 还在拖拽过程中
  102. if (_this.dragging) {
  103. _this._emitEvent('drag', ev, pointInfo, _this.draggingShape);
  104. }
  105. // 说明从某个图形直接移动到了画布外面,
  106. // 修复了 mouseleave 的 bug 后不再出现这种情况
  107. // if (this.currentShape) {
  108. // this._emitEvent('mouseleave', ev, pointInfo, this.currentShape, this.currentShape, null);
  109. // this.currentShape = null;
  110. // }
  111. }
  112. }
  113. };
  114. // 在 document 上处理拖拽到外面,释放鼠标时触发 dragend
  115. this._onDocumentMouseUp = function (ev) {
  116. var canvas = _this.canvas;
  117. var el = canvas.get('el');
  118. if (el !== ev.target) {
  119. // 不在 canvas 上移动
  120. if (_this.dragging) {
  121. var pointInfo = _this._getPointInfo(ev);
  122. if (_this.draggingShape) {
  123. // 如果存在拖拽的图形,则也触发 drop 事件
  124. _this._emitEvent('drop', ev, pointInfo, null);
  125. }
  126. _this._emitEvent('dragend', ev, pointInfo, _this.draggingShape);
  127. _this._afterDrag(_this.draggingShape, pointInfo, ev);
  128. }
  129. }
  130. };
  131. this.canvas = cfg.canvas;
  132. }
  133. EventController.prototype.init = function () {
  134. this._bindEvents();
  135. };
  136. // 注册事件
  137. EventController.prototype._bindEvents = function () {
  138. var _this = this;
  139. var el = this.canvas.get('el');
  140. util_1.each(EVENTS, function (eventName) {
  141. el.addEventListener(eventName, _this._eventCallback);
  142. });
  143. if (document) {
  144. // 处理移动到外面没有触发 shape mouse leave 的事件
  145. // 处理拖拽到外部的问题
  146. document.addEventListener('mousemove', this._onDocumentMove);
  147. // 处理拖拽过程中在外部释放鼠标的问题
  148. document.addEventListener('mouseup', this._onDocumentMouseUp);
  149. }
  150. };
  151. // 清理事件
  152. EventController.prototype._clearEvents = function () {
  153. var _this = this;
  154. var el = this.canvas.get('el');
  155. util_1.each(EVENTS, function (eventName) {
  156. el.removeEventListener(eventName, _this._eventCallback);
  157. });
  158. if (document) {
  159. document.removeEventListener('mousemove', this._onDocumentMove);
  160. document.removeEventListener('mouseup', this._onDocumentMouseUp);
  161. }
  162. };
  163. EventController.prototype._getEventObj = function (type, event, point, target, fromShape, toShape) {
  164. var eventObj = new graph_event_1.default(type, event);
  165. eventObj.fromShape = fromShape;
  166. eventObj.toShape = toShape;
  167. eventObj.x = point.x;
  168. eventObj.y = point.y;
  169. eventObj.clientX = point.clientX;
  170. eventObj.clientY = point.clientY;
  171. eventObj.propagationPath.push(target);
  172. // 事件的x,y应该是基于画布左上角的,与canvas的matrix无关
  173. return eventObj;
  174. };
  175. // 根据点获取图形,提取成独立方法,便于后续优化
  176. EventController.prototype._getShape = function (point, ev) {
  177. return this.canvas.getShape(point.x, point.y, ev);
  178. };
  179. // 获取事件的当前点的信息
  180. EventController.prototype._getPointInfo = function (ev) {
  181. var canvas = this.canvas;
  182. var clientPoint = canvas.getClientByEvent(ev);
  183. var point = canvas.getPointByEvent(ev);
  184. return {
  185. x: point.x,
  186. y: point.y,
  187. clientX: clientPoint.x,
  188. clientY: clientPoint.y,
  189. };
  190. };
  191. // 触发事件
  192. EventController.prototype._triggerEvent = function (type, ev) {
  193. var pointInfo = this._getPointInfo(ev);
  194. // 每次都获取图形有一定成本,后期可以考虑进行缓存策略
  195. var shape = this._getShape(pointInfo, ev);
  196. var method = this["_on" + type];
  197. var leaveCanvas = false;
  198. if (method) {
  199. method.call(this, pointInfo, shape, ev);
  200. }
  201. else {
  202. var preShape = this.currentShape;
  203. // 如果进入、移出画布时存在图形,则要分别触发事件
  204. if (type === 'mouseenter' || type === 'dragenter' || type === 'mouseover') {
  205. this._emitEvent(type, ev, pointInfo, null, null, shape); // 先进入画布
  206. if (shape) {
  207. this._emitEvent(type, ev, pointInfo, shape, null, shape); // 再触发图形的事件
  208. }
  209. if (type === 'mouseenter' && this.draggingShape) {
  210. // 如果正在拖拽图形, 则触发 dragleave
  211. this._emitEvent('dragenter', ev, pointInfo, null);
  212. }
  213. }
  214. else if (type === 'mouseleave' || type === 'dragleave' || type === 'mouseout') {
  215. leaveCanvas = true;
  216. if (preShape) {
  217. this._emitEvent(type, ev, pointInfo, preShape, preShape, null); // 先触发图形的事件
  218. }
  219. this._emitEvent(type, ev, pointInfo, null, preShape, null); // 再触发离开画布事件
  220. if (type === 'mouseleave' && this.draggingShape) {
  221. this._emitEvent('dragleave', ev, pointInfo, null);
  222. }
  223. }
  224. else {
  225. this._emitEvent(type, ev, pointInfo, shape, null, null); // 一般事件中不需要考虑 from, to
  226. }
  227. }
  228. if (!leaveCanvas) {
  229. this.currentShape = shape;
  230. }
  231. // 当鼠标从画布移动到 shape 或者从 preShape 移动到 shape 时,应用 shape 上的鼠标样式
  232. if (shape && !shape.get('destroyed')) {
  233. var canvas = this.canvas;
  234. var el = canvas.get('el');
  235. el.style.cursor = shape.attr('cursor') || canvas.get('cursor');
  236. }
  237. };
  238. // 记录下点击的位置、图形,便于拖拽事件、click 事件的判定
  239. EventController.prototype._onmousedown = function (pointInfo, shape, event) {
  240. // 只有鼠标左键的 mousedown 事件才会设置 mousedownShape 等属性,避免鼠标右键的 mousedown 事件引起其他事件发生
  241. if (event.button === LEFT_BTN_CODE) {
  242. this.mousedownShape = shape;
  243. this.mousedownPoint = pointInfo;
  244. this.mousedownTimeStamp = event.timeStamp;
  245. }
  246. this._emitEvent('mousedown', event, pointInfo, shape, null, null); // mousedown 不考虑fromShape, toShape
  247. };
  248. // mouseleave 和 mouseenter 都是成对存在的
  249. // mouseenter 和 mouseover 同时触发
  250. EventController.prototype._emitMouseoverEvents = function (event, pointInfo, fromShape, toShape) {
  251. var el = this.canvas.get('el');
  252. if (fromShape !== toShape) {
  253. if (fromShape) {
  254. this._emitEvent('mouseout', event, pointInfo, fromShape, fromShape, toShape);
  255. this._emitEvent('mouseleave', event, pointInfo, fromShape, fromShape, toShape);
  256. // 当鼠标从 fromShape 移动到画布上时,重置鼠标样式
  257. if (!toShape || toShape.get('destroyed')) {
  258. el.style.cursor = this.canvas.get('cursor');
  259. }
  260. }
  261. if (toShape) {
  262. this._emitEvent('mouseover', event, pointInfo, toShape, fromShape, toShape);
  263. this._emitEvent('mouseenter', event, pointInfo, toShape, fromShape, toShape);
  264. }
  265. }
  266. };
  267. // dragover 不等同于 mouseover,而等同于 mousemove
  268. EventController.prototype._emitDragoverEvents = function (event, pointInfo, fromShape, toShape, isCanvasEmit) {
  269. if (toShape) {
  270. if (toShape !== fromShape) {
  271. if (fromShape) {
  272. this._emitEvent('dragleave', event, pointInfo, fromShape, fromShape, toShape);
  273. }
  274. this._emitEvent('dragenter', event, pointInfo, toShape, fromShape, toShape);
  275. }
  276. if (!isCanvasEmit) {
  277. this._emitEvent('dragover', event, pointInfo, toShape);
  278. }
  279. }
  280. else if (fromShape) {
  281. // TODO: 此处判断有问题,当 drag 图形时,也会触发一次 dragleave 事件,因为此时 toShape 为 null,这不是所期望的
  282. // 经过空白区域
  283. this._emitEvent('dragleave', event, pointInfo, fromShape, fromShape, toShape);
  284. }
  285. if (isCanvasEmit) {
  286. this._emitEvent('dragover', event, pointInfo, toShape);
  287. }
  288. };
  289. // drag 完成后,需要做一些清理工作
  290. EventController.prototype._afterDrag = function (draggingShape, pointInfo, event) {
  291. if (draggingShape) {
  292. draggingShape.set('capture', true); // 恢复可以拾取
  293. this.draggingShape = null;
  294. }
  295. this.dragging = false;
  296. // drag 完成后,有可能 draggingShape 已经移动到了当前位置,所以不能直接取当前图形
  297. var shape = this._getShape(pointInfo, event);
  298. // 拖拽完成后,进行 enter,leave 的判定
  299. if (shape !== draggingShape) {
  300. this._emitMouseoverEvents(event, pointInfo, draggingShape, shape);
  301. }
  302. this.currentShape = shape; // 更新当前 shape,如果不处理当前图形的 mouseleave 事件可能会出问题
  303. };
  304. // 按键抬起时,会终止拖拽、触发点击
  305. EventController.prototype._onmouseup = function (pointInfo, shape, event) {
  306. // eevent.button === 0 表示鼠标左键事件,此处加上判断主要是为了避免右键鼠标会触发 mouseup 和 click 事件
  307. // ref: https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/button
  308. if (event.button === LEFT_BTN_CODE) {
  309. var draggingShape = this.draggingShape;
  310. if (this.dragging) {
  311. // 存在可以拖拽的图形,同时拖拽到其他图形上时触发 drag 事件
  312. if (draggingShape) {
  313. this._emitEvent('drop', event, pointInfo, shape);
  314. }
  315. this._emitEvent('dragend', event, pointInfo, draggingShape);
  316. this._afterDrag(draggingShape, pointInfo, event);
  317. }
  318. else {
  319. this._emitEvent('mouseup', event, pointInfo, shape); // 先触发 mouseup 再触发 click
  320. if (shape === this.mousedownShape) {
  321. this._emitEvent('click', event, pointInfo, shape);
  322. }
  323. this.mousedownShape = null;
  324. this.mousedownPoint = null;
  325. }
  326. }
  327. };
  328. // 当触发浏览器的 dragover 事件时,不会再触发 mousemove ,所以这时候的 dragenter, dragleave 事件需要重新处理
  329. EventController.prototype._ondragover = function (pointInfo, shape, event) {
  330. event.preventDefault(); // 如果不对 dragover 进行 preventDefault,则不会在 canvas 上触发 drop 事件
  331. var preShape = this.currentShape;
  332. this._emitDragoverEvents(event, pointInfo, preShape, shape, true);
  333. };
  334. // 大量的图形事件,都通过 mousemove 模拟
  335. EventController.prototype._onmousemove = function (pointInfo, shape, event) {
  336. var canvas = this.canvas;
  337. var preShape = this.currentShape;
  338. var draggingShape = this.draggingShape;
  339. // 正在拖拽时
  340. if (this.dragging) {
  341. // 正在拖拽中
  342. if (draggingShape) {
  343. // 如果拖拽了 shape 会触发 dragenter, dragleave, dragover 和 drag 事件
  344. this._emitDragoverEvents(event, pointInfo, preShape, shape, false);
  345. }
  346. // 如果存在 draggingShape 则会在 draggingShape 上触发 drag 事件,冒泡到 canvas 上
  347. // 否则在 canvas 上触发 drag 事件
  348. this._emitEvent('drag', event, pointInfo, draggingShape);
  349. }
  350. else {
  351. var mousedownPoint = this.mousedownPoint;
  352. if (mousedownPoint) {
  353. // 当鼠标点击下去,同时移动时,进行 drag 判定
  354. var mousedownShape = this.mousedownShape;
  355. var now = event.timeStamp;
  356. var timeWindow = now - this.mousedownTimeStamp;
  357. var dx = mousedownPoint.clientX - pointInfo.clientX;
  358. var dy = mousedownPoint.clientY - pointInfo.clientY;
  359. var dist = dx * dx + dy * dy;
  360. if (timeWindow > 120 || dist > CLICK_OFFSET) {
  361. if (mousedownShape && mousedownShape.get('draggable')) {
  362. // 设置了 draggable 的 shape 才能触发 drag 相关的事件
  363. draggingShape = this.mousedownShape; // 拖动鼠标点下时的 shape
  364. draggingShape.set('capture', false); // 禁止继续拾取,否则无法进行 dragover,dragenter,dragleave,drop的判定
  365. this.draggingShape = draggingShape;
  366. this.dragging = true;
  367. this._emitEvent('dragstart', event, pointInfo, draggingShape);
  368. // 清理按下鼠标时缓存的值
  369. this.mousedownShape = null;
  370. this.mousedownPoint = null;
  371. }
  372. else if (!mousedownShape && canvas.get('draggable')) {
  373. // 设置了 draggable 的 canvas 才能触发 drag 相关的事件
  374. this.dragging = true;
  375. this._emitEvent('dragstart', event, pointInfo, null);
  376. // 清理按下鼠标时缓存的值
  377. this.mousedownShape = null;
  378. this.mousedownPoint = null;
  379. }
  380. else {
  381. this._emitMouseoverEvents(event, pointInfo, preShape, shape);
  382. this._emitEvent('mousemove', event, pointInfo, shape);
  383. }
  384. }
  385. else {
  386. this._emitMouseoverEvents(event, pointInfo, preShape, shape);
  387. this._emitEvent('mousemove', event, pointInfo, shape);
  388. }
  389. }
  390. else {
  391. // 没有按键按下时,则直接触发 mouse over 相关的各种事件
  392. this._emitMouseoverEvents(event, pointInfo, preShape, shape);
  393. // 始终触发移动
  394. this._emitEvent('mousemove', event, pointInfo, shape);
  395. }
  396. }
  397. };
  398. // 触发事件
  399. EventController.prototype._emitEvent = function (type, event, pointInfo, shape, fromShape, toShape) {
  400. var eventObj = this._getEventObj(type, event, pointInfo, shape, fromShape, toShape);
  401. // 存在 shape 触发,则进行冒泡处理
  402. if (shape) {
  403. eventObj.shape = shape;
  404. // 触发 shape 上的事件
  405. emitTargetEvent(shape, type, eventObj);
  406. var parent_1 = shape.getParent();
  407. // 执行冒泡
  408. while (parent_1) {
  409. // 委托事件要先触发
  410. parent_1.emitDelegation(type, eventObj);
  411. // 事件冒泡停止,不能妨碍委托事件
  412. if (!eventObj.propagationStopped) {
  413. bubbleEvent(parent_1, type, eventObj);
  414. }
  415. eventObj.propagationPath.push(parent_1);
  416. parent_1 = parent_1.getParent();
  417. }
  418. }
  419. else {
  420. // 如果没有 shape 直接在 canvas 上触发
  421. var canvas = this.canvas;
  422. // 直接触发 canvas 上的事件
  423. emitTargetEvent(canvas, type, eventObj);
  424. }
  425. };
  426. EventController.prototype.destroy = function () {
  427. // 清理事件
  428. this._clearEvents();
  429. // 清理缓存的对象
  430. this.canvas = null;
  431. this.currentShape = null;
  432. this.draggingShape = null;
  433. this.mousedownPoint = null;
  434. this.mousedownShape = null;
  435. this.mousedownTimeStamp = null;
  436. };
  437. return EventController;
  438. }());
  439. exports.default = EventController;
  440. //# sourceMappingURL=event-contoller.js.map