hookehuyr

feat(animation): 添加SVG图像到动画路径组件

在动画路径组件中新增了SVG图像显示功能,支持根据激活节点动态切换图像。引入了多个SVG资源文件,并更新了动画路径的逻辑以支持图像显示。
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
1 <!-- 1 <!--
2 * @Date: 2025-03-28 09:23:04 2 * @Date: 2025-03-28 09:23:04
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-03-28 14:41:28 4 + * @LastEditTime: 2025-03-28 21:28:07
5 * @FilePath: /mlaj/src/views/animation.vue 5 * @FilePath: /mlaj/src/views/animation.vue
6 * @Description: 贝塞尔曲线动画路径组件 6 * @Description: 贝塞尔曲线动画路径组件
7 * 7 *
...@@ -13,8 +13,14 @@ ...@@ -13,8 +13,14 @@
13 --> 13 -->
14 <template> 14 <template>
15 <div class="animation-container"> 15 <div class="animation-container">
16 - <div style="position: fixed; top: 0; right: 0;"> 16 + <div style="position: fixed; top: 0; right: 0">
17 - <button class="next-button" @click="nextStep" :disabled="activeNodeIndex >= points.length - 1">下一步</button> 17 + <button
18 + class="next-button"
19 + @click="nextStep"
20 + :disabled="activeNodeIndex >= points.length - 1"
21 + >
22 + 下一步
23 + </button>
18 </div> 24 </div>
19 <svg width="100%" height="100%" viewBox="0 0 1000 1200" class="animation-svg"> 25 <svg width="100%" height="100%" viewBox="0 0 1000 1200" class="animation-svg">
20 <!-- 定义箭头标记 --> 26 <!-- 定义箭头标记 -->
...@@ -79,12 +85,29 @@ ...@@ -79,12 +85,29 @@
79 :class="['node', { 'node-active': activeNodeIndex >= index - 1 }]" 85 :class="['node', { 'node-active': activeNodeIndex >= index - 1 }]"
80 /> 86 />
81 </template> 87 </template>
88 +
89 + <!-- Image In Svg -->
90 + <template v-for="(obj, index) in svgObj" :key="obj.name">
91 + <image
92 + :id="obj.name"
93 + :href="index <= activeNodeIndex ? obj.light_up : obj.not_light"
94 + width="100"
95 + height="100"
96 + :x="points[0].x + obj.offset.x"
97 + :y="points[0].y + obj.offset.y"
98 + />
99 + </template>
82 </svg> 100 </svg>
83 </div> 101 </div>
84 </template> 102 </template>
85 103
86 <script setup> 104 <script setup>
87 -import { ref, computed } from 'vue'; 105 +import { ref, computed } from "vue";
106 +import img_svg1 from "@/assets/1.svg";
107 +import img_svg2 from "@/assets/2.svg";
108 +import img_svg3 from "@/assets/3.svg";
109 +import img_svg4 from "@/assets/4.svg";
110 +import img_svg5 from "@/assets/5.svg";
88 111
89 // 定义节点坐标数组,每个节点包含x和y坐标 112 // 定义节点坐标数组,每个节点包含x和y坐标
90 // 这些点将用于生成贝塞尔曲线的路径 113 // 这些点将用于生成贝塞尔曲线的路径
...@@ -93,7 +116,7 @@ const points = ref([ ...@@ -93,7 +116,7 @@ const points = ref([
93 { x: 300, y: 300 }, 116 { x: 300, y: 300 },
94 { x: 700, y: 600 }, 117 { x: 700, y: 600 },
95 { x: 300, y: 800 }, 118 { x: 300, y: 800 },
96 - { x: 700, y: 1000 } 119 + { x: 700, y: 1000 },
97 ]); 120 ]);
98 121
99 // 当前激活的节点索引,初始值为-1表示没有节点被激活 122 // 当前激活的节点索引,初始值为-1表示没有节点被激活
...@@ -114,10 +137,10 @@ const pathData = computed(() => { ...@@ -114,10 +137,10 @@ const pathData = computed(() => {
114 } else { 137 } else {
115 const prevPoint = points.value[index - 1]; 138 const prevPoint = points.value[index - 1];
116 const segment = calculatePathSegment(prevPoint, point); 139 const segment = calculatePathSegment(prevPoint, point);
117 - path.push(segment.substring(segment.indexOf('C'))); 140 + path.push(segment.substring(segment.indexOf("C")));
118 } 141 }
119 }); 142 });
120 - return path.join(' '); 143 + return path.join(" ");
121 }); 144 });
122 145
123 // 计算两点之间的贝塞尔曲线路径段 146 // 计算两点之间的贝塞尔曲线路径段
...@@ -141,11 +164,11 @@ const calculatePathSegment = (startPoint, endPoint) => { ...@@ -141,11 +164,11 @@ const calculatePathSegment = (startPoint, endPoint) => {
141 // 计算偏移后的起点和终点,根据路径方向调整垂直偏移 164 // 计算偏移后的起点和终点,根据路径方向调整垂直偏移
142 const adjustedStart = { 165 const adjustedStart = {
143 x: startPoint.x + unitX * offset, 166 x: startPoint.x + unitX * offset,
144 - y: startPoint.y + (dy > 0 ? -verticalOffset : verticalOffset) 167 + y: startPoint.y + (dy > 0 ? -verticalOffset : verticalOffset),
145 }; 168 };
146 const adjustedEnd = { 169 const adjustedEnd = {
147 x: endPoint.x - unitX * offset, 170 x: endPoint.x - unitX * offset,
148 - y: endPoint.y + (dy > 0 ? verticalOffset : -verticalOffset) 171 + y: endPoint.y + (dy > 0 ? verticalOffset : -verticalOffset),
149 }; 172 };
150 173
151 // 计算控制点 174 // 计算控制点
...@@ -160,10 +183,9 @@ const calculatePathSegment = (startPoint, endPoint) => { ...@@ -160,10 +183,9 @@ const calculatePathSegment = (startPoint, endPoint) => {
160 // 未激活路径的颜色 183 // 未激活路径的颜色
161 // 使用浅灰色(#ccc)表示未激活状态 184 // 使用浅灰色(#ccc)表示未激活状态
162 const pathColor = computed(() => { 185 const pathColor = computed(() => {
163 - return '#ccc'; 186 + return "#ccc";
164 }); 187 });
165 188
166 -
167 // 处理节点点击事件,激活指定节点并更新路径 189 // 处理节点点击事件,激活指定节点并更新路径
168 // @param index - 被点击节点的索引 190 // @param index - 被点击节点的索引
169 // 点击节点时会激活该节点及其之前的所有节点和路径 191 // 点击节点时会激活该节点及其之前的所有节点和路径
...@@ -197,11 +219,60 @@ const nextStep = () => { ...@@ -197,11 +219,60 @@ const nextStep = () => {
197 activePathSegments.value.push(newSegment); 219 activePathSegments.value.push(newSegment);
198 } 220 }
199 // 如果是最后一个节点,直接将其设置为激活状态 221 // 如果是最后一个节点,直接将其设置为激活状态
200 - if (activeNodeIndex.value === points.value.length - 2) { 222 + if (activeNodeIndex.value === points.value.length - 1) {
201 activeNodeIndex.value = points.value.length - 1; 223 activeNodeIndex.value = points.value.length - 1;
202 } 224 }
203 } 225 }
204 }; 226 };
227 +
228 +// 图片对象数组
229 +const svgObj = [
230 + {
231 + name: "node1",
232 + not_light: img_svg1,
233 + light_up: img_svg5,
234 + offset: {
235 + x: -350,
236 + y: -25,
237 + },
238 + },
239 + {
240 + name: "node2",
241 + not_light: img_svg2,
242 + light_up: img_svg4,
243 + offset: {
244 + x: -150,
245 + y: 200,
246 + },
247 + },
248 + {
249 + name: "node3",
250 + not_light: img_svg3,
251 + light_up: img_svg3,
252 + offset: {
253 + x: -450,
254 + y: 480,
255 + },
256 + },
257 + {
258 + name: "node4",
259 + not_light: img_svg4,
260 + light_up: img_svg2,
261 + offset: {
262 + x: -150,
263 + y: 700,
264 + },
265 + },
266 + {
267 + name: "node5",
268 + not_light: img_svg5,
269 + light_up: img_svg1,
270 + offset: {
271 + x: -450,
272 + y: 950,
273 + },
274 + },
275 +];
205 </script> 276 </script>
206 277
207 <style scoped> 278 <style scoped>
...@@ -247,7 +318,7 @@ const nextStep = () => { ...@@ -247,7 +318,7 @@ const nextStep = () => {
247 318
248 .node-active { 319 .node-active {
249 fill: white; 320 fill: white;
250 - stroke: #4CAF50; 321 + stroke: #4caf50;
251 } 322 }
252 323
253 .path-animation { 324 .path-animation {
...@@ -269,7 +340,7 @@ const nextStep = () => { ...@@ -269,7 +340,7 @@ const nextStep = () => {
269 top: 1.25rem; 340 top: 1.25rem;
270 right: 1.25rem; 341 right: 1.25rem;
271 padding: 0.5rem 1rem; 342 padding: 0.5rem 1rem;
272 - background-color: #4CAF50; 343 + background-color: #4caf50;
273 color: white; 344 color: white;
274 border: none; 345 border: none;
275 border-radius: 0.25rem; 346 border-radius: 0.25rem;
......