|
|
@@ -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>
|