animationEdge.vue 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. <script setup lang="ts">
  2. import { computed, nextTick, ref, toRef, watch } from "vue";
  3. import { TransitionPresets, executeTransition } from "@vueuse/core";
  4. import {
  5. Position,
  6. BaseEdge,
  7. useVueFlow,
  8. useNodesData,
  9. getSmoothStepPath,
  10. EdgeLabelRenderer
  11. } from "@vue-flow/core";
  12. const props = defineProps({
  13. id: {
  14. type: String,
  15. required: true
  16. },
  17. source: {
  18. type: String,
  19. required: true
  20. },
  21. target: {
  22. type: String,
  23. required: true
  24. },
  25. sourceX: {
  26. type: Number,
  27. required: true
  28. },
  29. sourceY: {
  30. type: Number,
  31. required: true
  32. },
  33. targetX: {
  34. type: Number,
  35. required: true
  36. },
  37. targetY: {
  38. type: Number,
  39. required: true
  40. },
  41. sourcePosition: {
  42. type: String,
  43. default: Position.Right
  44. },
  45. targetPosition: {
  46. type: String,
  47. default: Position.Left
  48. }
  49. });
  50. const { findEdge } = useVueFlow();
  51. const nodesData = useNodesData([props.target, props.source]);
  52. const edgePoint = ref(0);
  53. const edgeRef = ref();
  54. const labelPosition = ref({ x: 0, y: 0 });
  55. const currentLength = ref(0);
  56. const targetNodeData = toRef(() => nodesData.value[0].data);
  57. const sourceNodeData = toRef(() => nodesData.value[1].data);
  58. const isFinished = toRef(() => sourceNodeData.value.isFinished);
  59. const isCancelled = toRef(() => targetNodeData.value.isCancelled);
  60. const isAnimating = ref(false);
  61. const edgeColor = toRef(() => {
  62. if (targetNodeData.value.hasError) {
  63. return "#f87171";
  64. }
  65. if (targetNodeData.value.isFinished) {
  66. return "#42B983";
  67. }
  68. if (targetNodeData.value.isCancelled || targetNodeData.value.isSkipped) {
  69. return "#fbbf24";
  70. }
  71. if (targetNodeData.value.isRunning || isAnimating.value) {
  72. return "#2563eb";
  73. }
  74. return "#6b7280";
  75. });
  76. // @ts-expect-error
  77. const path = computed(() => getSmoothStepPath(props));
  78. watch(isCancelled, isCancelled => {
  79. if (isCancelled) {
  80. reset();
  81. }
  82. });
  83. watch(isAnimating, isAnimating => {
  84. const edge = findEdge(props.id);
  85. if (edge) {
  86. edge.data = {
  87. ...edge.data,
  88. isAnimating
  89. };
  90. }
  91. });
  92. watch(edgePoint, point => {
  93. const pathEl = edgeRef.value?.pathEl;
  94. if (!pathEl || point === 0 || !isAnimating.value) {
  95. return;
  96. }
  97. const nextLength = pathEl.getTotalLength();
  98. if (currentLength.value !== nextLength) {
  99. runAnimation();
  100. return;
  101. }
  102. labelPosition.value = pathEl.getPointAtLength(point);
  103. });
  104. watch(isFinished, isFinished => {
  105. if (isFinished) {
  106. runAnimation();
  107. }
  108. });
  109. async function runAnimation() {
  110. const pathEl = edgeRef.value?.pathEl;
  111. if (!pathEl) {
  112. return;
  113. }
  114. const totalLength = pathEl.getTotalLength();
  115. const from = edgePoint.value || 0;
  116. labelPosition.value = pathEl.getPointAtLength(from);
  117. isAnimating.value = true;
  118. if (currentLength.value !== totalLength) {
  119. currentLength.value = totalLength;
  120. }
  121. await executeTransition(edgePoint, from, totalLength, {
  122. transition: TransitionPresets.easeInOutCubic,
  123. duration: Math.max(1500, totalLength / 2),
  124. abort: () => !isAnimating.value
  125. });
  126. reset();
  127. }
  128. function reset() {
  129. nextTick(() => {
  130. edgePoint.value = 0;
  131. currentLength.value = 0;
  132. labelPosition.value = { x: 0, y: 0 };
  133. isAnimating.value = false;
  134. });
  135. }
  136. </script>
  137. <template>
  138. <BaseEdge
  139. :id="id"
  140. ref="edgeRef"
  141. :path="path[0]"
  142. :style="{ stroke: edgeColor }"
  143. />
  144. <EdgeLabelRenderer v-if="isAnimating">
  145. <div
  146. :style="{
  147. transform: `translate(-50%, -50%) translate(${labelPosition.x}px,${labelPosition.y}px)`
  148. }"
  149. class="nodrag nopan animated-edge-label"
  150. >
  151. <span class="truck">
  152. <span class="box">📦</span>
  153. 🚚
  154. </span>
  155. </div>
  156. </EdgeLabelRenderer>
  157. </template>
  158. <style scoped>
  159. .animated-edge-label {
  160. position: absolute;
  161. z-index: 100;
  162. }
  163. .truck {
  164. position: relative;
  165. display: inline-block;
  166. transform: scaleX(-1);
  167. }
  168. .box {
  169. position: absolute;
  170. top: -10px;
  171. }
  172. </style>