Browse Source

增加svg动画同步demo

caner 1 year ago
parent
commit
6580d1553c

+ 3 - 1
README.md

@@ -22,4 +22,6 @@
 11. '/video' 荧石视频SDK-Demo
 11. '/video' 荧石视频SDK-Demo
 ![image](./public/imgs/11.png)
 ![image](./public/imgs/11.png)
 12. '/geo' CAD瓦片反差图-Demo
 12. '/geo' CAD瓦片反差图-Demo
-![image](./public/imgs/12.png)
+![image](./public/imgs/12.png)
+13. '/geo' svg动画同步-Demo
+![image](./public/imgs/13.png)

BIN
public/imgs/13.png


BIN
src/pages/svgAnimation/img/1.png


BIN
src/pages/svgAnimation/img/2.png


BIN
src/pages/svgAnimation/img/3.png


+ 51 - 0
src/pages/svgAnimation/img/test.svg

@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg width="600" height="400" viewBox="0 0 600 400" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <g>
+    <!-- 复杂路径 -->
+    <!-- <path id="complexPath" d="M50,50 C100,25 150,75 200,50 S300,25 350,50 Q400,100 450,50  T550,50"  fill="none" stroke="none" stroke-width="2"/> -->
+        <!-- 可见的路径(新增样式和动画) -->
+    <path id="complexPath"
+          d="M50,50 C100,25 150,75 200,50 S300,25 350,50 Q400,100 450,50 T550,50"
+          fill="none"
+          stroke="#3498db"
+          stroke-width="3"
+          stroke-dasharray="0"  
+          stroke-dashoffset="0">
+      
+      <!-- 路径绘制动画 -->
+      <animate id="pathAnim"
+               attributeName="stroke-dasharray"
+               begin="0s"
+               dur="8s"
+               values="0, 1000; 1000, 0"  
+               calcMode="linear"
+               fill="freeze"/>
+    </path>
+    <!-- 运动对象 -->
+    <path d="M-15,-5 L0,-15 L15,-5 L5,5 L-5,5 Z" fill="red">
+      <animateMotion 
+        begin="pathAnim.begin" 
+        dur="8s" 
+        rotate="auto" 
+        fill="freeze">
+        <mpath xlink:href="#complexPath"/>
+      </animateMotion>
+    </path>
+  </g>
+    <script type="application/ecmascript">
+    <![CDATA[
+    (function() {
+      // 获取路径元素
+      const path = document.getElementById('complexPath');
+      
+      // 计算实际路径长度
+      const pathLength = Math.ceil(path.getTotalLength());
+      
+      // 设置路径动画参数
+      const pathAnim = document.getElementById('pathAnim');
+      path.setAttribute('stroke-dasharray', `0, ${pathLength}`);
+      pathAnim.setAttribute('values', `0, ${pathLength}; ${pathLength}, 0`);
+    })();
+    ]]>
+  </script>
+</svg>

+ 170 - 0
src/pages/svgAnimation/index.vue

@@ -0,0 +1,170 @@
+<template>
+  <div class="box">
+    <svg
+      :viewBox="`0 0 ${svgConfig.width} ${svgConfig.height}`"
+      preserveAspectRatio="none"
+      version="1.1"
+      xmlns="http://www.w3.org/2000/svg"
+      xmlns:xlink="http://www.w3.org/1999/xlink"
+    >
+      <!-- 底色 -->
+      <image
+        v-for="(item, index) in svgConfig.tunnel"
+        :key="index"
+        :href="item.img"
+        :x="item.x"
+        :y="item.y"
+        :width="item.w"
+        :height="item.h"
+        preserveAspectRatio="none"
+      />
+
+      <!-- 运动对象 -->
+      <g
+        v-for="(item, index) in list"
+        :key="index"
+      >
+        <!-- 运动路径+动画 -->
+        <path
+          v-if="item.showPath"
+          :id="item.pathId"
+          :d="item.path"
+          fill="none"
+          stroke="#3498db"
+          stroke-width="1"
+          :stroke-dasharray="item.pathStorkeDasharray"
+          stroke-dashoffset="0"
+        >
+          <animate
+            :id="item.animoId"
+            attributeName="stroke-dasharray"
+            begin="0s"
+            :dur="`${item.duration}s`"
+            :values="item.animoValues"
+            calcMode="linear"
+            fill="freeze"
+          />
+        </path>
+        <!-- 对象 -->
+        <image
+          :href="item.img"
+          :x="item.x"
+          :y="item.y"
+          :width="item.w"
+          :height="item.h"
+          :transform="item.transform"
+          preserveAspectRatio="none"
+        >
+          <animateMotion
+            :begin="`${item.animoId}.begin`"
+            :dur="`${item.duration}s`"
+            fill="freeze"
+          >
+            <mpath :xlink:href="`#${item.pathId}`" />
+          </animateMotion>
+        </image>
+      </g>
+    </svg>
+  </div>
+</template>
+
+<script setup lang='ts'>
+import { onMounted, reactive, ref } from 'vue'
+
+const props = withDefaults(defineProps<{
+  data?: { stake: number, img: string, stakes: number[] }[], // 原始数据
+  showPath?: boolean, // 是否显示路径
+  isIn?: boolean // 进口|出口
+}>(), {
+  isIn: true,
+  showPath: true,
+  data: () => [ {
+    stake: 200, img: new URL('./img/3.png', import.meta.url).href, name: '台车', stakes: [ 0, 10, 20, 30, 40, 50, 60, 70, 80, 100, 120, 140, 160, 180, 200 ]
+  } ]
+})
+// svg 配置
+const svgConfig = reactive({
+  width: 600,
+  height: 400,
+  tunnel: [
+    {
+      type: 1, x: 0, y: 0, w: 400, h: 400, img: new URL('./img/2.png', import.meta.url).href
+    },
+    {
+      type: 0, x: 400, y: 0, w: 200, h: 400, img: new URL('./img/1.png', import.meta.url).href
+    }
+  ]
+})
+const list = ref([] as { [key: string]: any }[])
+
+// 计算path 路径
+function countPath(path: number[]) {
+  let str = 'M'
+  const y = Math.floor(Math.random() * (svgConfig.height / 2)) + (svgConfig.height / 4)
+  for (let k = 0; k < path.length; k++) {
+    const el = path[k]
+    str += ` ${el},${y}`
+  }
+  return str
+}
+
+// 更新数据
+function setData(data = props.data) {
+  list.value = data.map((el, index) => ({
+    ...el,
+    // 原始路径参数
+    path: countPath(el.stakes),
+    pathId: `PATH${index}`,
+    animoId: `Animo${index}`,
+    pathStorkeDasharray: '',
+    animoValues: '',
+    // 动画参数
+    duration: 8,
+    showPath: props.showPath,
+    // 图片参数
+    w: 33.9,
+    h: 60,
+    x: props.showPath ? -16.95 : el.stake,
+    y: props.showPath ? -60 : 60,
+    transform: props.isIn ? 'scale(-1, 1)' : '',
+    callBack() {
+      // 使动画同步
+      const pathDom = document.getElementById(this.pathId) as unknown as SVGPathElement
+      const pathLength = Math.ceil(pathDom?.getTotalLength())
+      this.pathStorkeDasharray = `0, ${pathLength}`
+      this.animoValues = `0, ${pathLength}; ${pathLength}, 0`
+      if (this.showPath) {
+        setTimeout(() => {
+          this.showPath = false
+        }, this.duration * 1000)
+      }
+    }
+  }))
+  setTimeout(() => {
+    for (let k = 0; k < list.value.length; k++) {
+      const el = list.value[k]
+      el.callBack()
+    }
+  }, 200)
+}
+
+onMounted(() => {
+  document.title = 'svg动画同步'
+  setData()
+})
+</script>
+
+<style lang="scss" scoped>
+.box {
+  width: 100vw;
+  height: 100vh;
+  background: black;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  svg {
+    width: 98%;
+    height: 800px;
+  }
+}
+</style>

+ 9 - 0
src/pages/svgAnimation/route.ts

@@ -0,0 +1,9 @@
+import { RouteRecordRaw } from 'vue-router'
+import face from './index.vue'
+export default {
+  path: '/svg',
+  meta: {
+    authorize: true
+  },
+  component: face
+} as RouteRecordRaw