feat(animation): 添加SVG图像到动画路径组件
在动画路径组件中新增了SVG图像显示功能,支持根据激活节点动态切换图像。引入了多个SVG资源文件,并更新了动画路径的逻辑以支持图像显示。
Showing
6 changed files
with
85 additions
and
14 deletions
src/assets/1.svg
0 → 100644
This diff is collapsed. Click to expand it.
src/assets/2.svg
0 → 100644
This diff is collapsed. Click to expand it.
src/assets/3.svg
0 → 100644
This diff is collapsed. Click to expand it.
src/assets/4.svg
0 → 100644
This diff is collapsed. Click to expand it.
src/assets/5.svg
0 → 100644
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; | ... | ... |
-
Please register or login to post a comment