锁定节点

G6 3.1.4 版本中新增了 lock()unlock()hasLocked() 三个 API,方便用户锁定某个节点。默认情况下,当锁定某个节点后,拖动节点时锁定的节点不会有任何反应,但拖动画布和缩放画布时,仍然会对锁定的节点有影响,如果不想让锁定的节点收到拖动画布和缩放画布的影响,可以通过自定义 Behavior 的方式来实现。

拖动画布时候不处理锁定的节点

G6 内置的 drag-canvas 不区分节点是否锁定,全部一视同仁。绝大数情况下,这种行为是完全没问题的,但某些业务可能会要求锁定的节点,拖动画布时也不能移动,对于这种情况,可以通过重新定义拖动画布的 Behavior 来实现。

  1. import G6 from '@antv/g6';
  2. const Util = G6.Util;
  3. const abs = Math.abs;
  4. const DRAG_OFFSET = 10;
  5. const body = document.body;
  6. const ALLOW_EVENTS = [16, 17, 18];
  7. G6.registerBehavior('drag-canvas-exclude-lockedNode', {
  8. getDefaultCfg() {
  9. return {
  10. direction: 'both',
  11. };
  12. },
  13. getEvents() {
  14. return {
  15. 'canvas:mousedown': 'onMouseDown',
  16. 'canvas:mousemove': 'onMouseMove',
  17. 'canvas:mouseup': 'onMouseUp',
  18. 'canvas:click': 'onMouseUp',
  19. 'canvas:mouseleave': 'onOutOfRange',
  20. keyup: 'onKeyUp',
  21. keydown: 'onKeyDown',
  22. };
  23. },
  24. updateViewport(e) {
  25. const origin = this.origin;
  26. const clientX = +e.clientX;
  27. const clientY = +e.clientY;
  28. if (isNaN(clientX) || isNaN(clientY)) {
  29. return;
  30. }
  31. let dx = clientX - origin.x;
  32. let dy = clientY - origin.y;
  33. if (this.get('direction') === 'x') {
  34. dy = 0;
  35. } else if (this.get('direction') === 'y') {
  36. dx = 0;
  37. }
  38. this.origin = {
  39. x: clientX,
  40. y: clientY,
  41. };
  42. // 和内置 drag-canvas 不同的地方是在这里
  43. const lockedNodes = this.graph.findAll('node', node => !node.hasLocked());
  44. lockedNodes.forEach(node => {
  45. node.get('group').translate(dx, dy);
  46. });
  47. this.graph.paint();
  48. },
  49. onMouseDown(e) {
  50. if (this.keydown) {
  51. return;
  52. }
  53. this.origin = { x: e.clientX, y: e.clientY };
  54. this.dragging = false;
  55. },
  56. onMouseMove(e) {
  57. if (this.keydown) {
  58. return;
  59. }
  60. e = Util.cloneEvent(e);
  61. const graph = this.graph;
  62. if (!this.origin) {
  63. return;
  64. }
  65. if (this.origin && !this.dragging) {
  66. if (
  67. abs(this.origin.x - e.clientX) + abs(this.origin.y - e.clientY) <
  68. DRAG_OFFSET
  69. ) {
  70. return;
  71. }
  72. if (this.shouldBegin.call(this, e)) {
  73. e.type = 'dragstart';
  74. graph.emit('canvas:dragstart', e);
  75. this.dragging = true;
  76. }
  77. }
  78. if (this.dragging) {
  79. e.type = 'drag';
  80. graph.emit('canvas:drag', e);
  81. }
  82. if (this.shouldUpdate.call(this, e)) {
  83. this.updateViewport(e);
  84. }
  85. },
  86. onMouseUp(e) {
  87. if (this.keydown) {
  88. return;
  89. }
  90. if (!this.dragging) {
  91. this.origin = null;
  92. return;
  93. }
  94. e = Util.cloneEvent(e);
  95. const graph = this.graph;
  96. if (this.shouldEnd.call(this, e)) {
  97. this.updateViewport(e);
  98. }
  99. e.type = 'dragend';
  100. graph.emit('canvas:dragend', e);
  101. this.endDrag();
  102. },
  103. endDrag() {
  104. if (this.dragging) {
  105. this.origin = null;
  106. this.dragging = false;
  107. // 终止时需要判断此时是否在监听画布外的 mouseup 事件,若有则解绑
  108. const fn = this.fn;
  109. if (fn) {
  110. body.removeEventListener('mouseup', fn, false);
  111. this.fn = null;
  112. }
  113. }
  114. },
  115. // 若在拖拽时,鼠标移出画布区域,此时放开鼠标无法终止 drag 行为。在画布外监听 mouseup 事件,放开则终止
  116. onOutOfRange(e) {
  117. if (this.dragging) {
  118. const self = this;
  119. const canvasElement = self.graph.get('canvas').get('el');
  120. const fn = ev => {
  121. if (ev.target !== canvasElement) {
  122. self.onMouseUp(e);
  123. }
  124. };
  125. this.fn = fn;
  126. body.addEventListener('mouseup', fn, false);
  127. }
  128. },
  129. onKeyDown(e) {
  130. const code = e.keyCode || e.which;
  131. if (!code) {
  132. return;
  133. }
  134. if (ALLOW_EVENTS.indexOf(code) > -1) {
  135. this.keydown = true;
  136. } else {
  137. this.keydown = false;
  138. }
  139. },
  140. onKeyUp() {
  141. this.keydown = false;
  142. },
  143. });

缩放画布时不处理锁定的节点

默认情况下,G6 内置的 zoom-canvas 在缩放画布时候也会对锁定的节点缩放,如果缩放过程中不需要操作锁定的节点,则可以通过下面的方式来实现。

  1. const DELTA = 0.05;
  2. G6.registerBehavior('zoom-canvas-exclude-lockedNode', {
  3. getDefaultCfg() {
  4. return {
  5. sensitivity: 2,
  6. minZoom: 0.1,
  7. maxZoom: 10,
  8. };
  9. },
  10. getEvents() {
  11. return {
  12. wheel: 'onWheel',
  13. };
  14. },
  15. onWheel(e) {
  16. e.preventDefault();
  17. if (!this.shouldUpdate.call(this, e)) {
  18. return;
  19. }
  20. const graph = this.graph;
  21. const canvas = graph.get('canvas');
  22. const point = canvas.getPointByClient(e.clientX, e.clientY);
  23. const pixelRatio = canvas.get('pixelRatio');
  24. const sensitivity = this.get('sensitivity');
  25. let ratio = graph.getZoom();
  26. // 兼容IE、Firefox及Chrome
  27. if (e.wheelDelta < 0) {
  28. ratio = 1 - DELTA * sensitivity;
  29. } else {
  30. ratio = 1 + DELTA * sensitivity;
  31. }
  32. const zoom = ratio * graph.getZoom();
  33. if (zoom > this.get('maxZoom') || zoom < this.get('minZoom')) {
  34. return;
  35. }
  36. graph.zoom(ratio, { x: point.x / pixelRatio, y: point.y / pixelRatio });
  37. const lockedNodes = this.graph.findAll('node', node => !node.hasLocked());
  38. lockedNodes.forEach(node => {
  39. const matrix = Util.clone(node.get('group').getMatrix());
  40. const center = node.getModel();
  41. Util.mat3.translate(matrix, matrix, [-center.x, -center.y]);
  42. Util.mat3.scale(matrix, matrix, [ratio, ratio]);
  43. Util.mat3.translate(matrix, matrix, [center.x, center.y]);
  44. node.get('group').setMatrix(matrix);
  45. });
  46. graph.paint();
  47. graph.emit('wheelzoom', e);
  48. },
  49. });