caner 1 year ago
parent
commit
db84b9d3d9
1 changed files with 283 additions and 0 deletions
  1. 283 0
      src/pages/svgAnimation/newIndex.vue

+ 283 - 0
src/pages/svgAnimation/newIndex.vue

@@ -0,0 +1,283 @@
+<template>
+    <div class="box">
+      <!-- preserveAspectRatio=none 拉伸 -->
+      <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"
+      >
+        <!-- 图片描边 -->
+        <defs>
+          <!-- 定义描边滤镜 -->
+          <filter
+            id="outline"
+            x="-10%"
+            y="-10%"
+            width="120%"
+            height="120%"
+          >
+            <!-- 扩张图片边缘 -->
+            <feMorphology
+              operator="dilate"
+              radius="3"
+              in="SourceAlpha"
+              result="thicker"
+            />
+            <!-- 描边颜色 -->
+            <feFlood
+              flood-color="#ff0000"
+              result="color"
+            />
+            <!-- 合并描边和原图 -->
+            <feComposite
+              in="color"
+              in2="thicker"
+              operator="in"
+              result="outline"
+            />
+            <feComposite
+              in="SourceGraphic"
+              in2="outline"
+              operator="over"
+            />
+          </filter>
+        </defs>
+        <!-- 底色 -->
+        <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"
+        >
+          <!-- 存运动路径+动画 -->
+          <g :id="item.pathId" />
+          <!-- 对象 -->
+          <image
+            :id="item.imgId"
+            :href="item.img"
+            :x="item.x"
+            :y="item.y"
+            :width="item.w"
+            :height="item.h"
+            :transform="item.transform"
+            preserveAspectRatio="none"
+            :filter="props.dataCheck === index ? 'url(#outline)':''"
+          />
+        </g>
+      </svg>
+      <!-- 图例 -->
+      <div class="box-leng">
+        <template
+          v-for="(item, index) in svgConfig.leng"
+          :key="index"
+        >
+          <div
+            class="box-leng-item"
+            :style="`left:${item.x}`"
+          >
+            <p>{{ item.name }}</p>
+            <p>{{ item.stake }}</p>
+          </div>
+        </template>
+      </div>
+    </div>
+  </template>
+  
+  <script setup lang='ts'>
+  import {
+    onMounted, reactive, ref, watch
+  } from 'vue'
+  import { mileage2string } from '@/utils/JsFn'
+  import img1 from './img/1.png'
+  import img2 from './img/2.png'
+  
+  const props = withDefaults(defineProps<{
+    data?: { img: string, postions:any[] }[], // 原始数据
+    dataCheck?:number, // 对象选中
+    startAnimate?:boolean, // 显示动画
+    menuItem?:{isIn:boolean, startStake:number, endStake:number, currentStake:number, length:number, stakeType:string},
+  }>(), {
+    menuItem: undefined,
+    data: undefined,
+    startAnimate: false,
+    dataCheck: -1
+  })
+  const emit = defineEmits<{(evt: 'update:startAnimate', value: boolean): void }>()
+  // svg 配置
+  const svgConfig = reactive({
+    width: 600,
+    height: 400,
+    tunnel: [],
+    leng: []
+  } as {[key:string]:any})
+  const list = ref([] as { [key: string]: any }[])
+  const timer = ref()
+  
+  // 计算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: any[]|undefined) {
+    if (!data || !data.length || !svgConfig.tunnel.length) {
+      list.value = []
+      return
+    }
+  
+    const scale = props.menuItem!.length / 1358 // 1358 dom宽度,比例
+    list.value = data.map((el, index) => {
+      const x = props.startAnimate ? -el.w / 2 : Math.floor(el.postions[el.postions.length - 1].mileage - props.menuItem!.startStake)
+      const y = props.startAnimate ? el.h ? -el.h : -el.w / scale : Math.floor(Math.random() * (svgConfig.height / 2)) + (svgConfig.height / 4)
+      const transform = el.isDevice && !props.menuItem!.isIn ? `translate(${x + (el.w / 2)},${y}) scale(-1, 1) translate(${-(x + (el.w / 2))},${-y})` : ''
+      const obj = {
+        ...el,
+        path: countPath(el.postions.map((es: { mileage: number; }) => Math.floor((es.mileage - props.menuItem!.startStake) * 10) / 10)),
+        pathId: `PATH${index}`,
+        imgId: `IMG${index}`,
+        duration: 8,
+        w: el.w,
+        h: el.h ? el.h : el.w / scale,
+        x,
+        y, // 随机Y
+        transform,
+        callBack() {
+          const g = document.getElementById(this.pathId)
+          // 创建path及动画
+          const pathChild = g?.childNodes
+          if (pathChild!.length) g?.replaceChildren()
+          // 移动目标
+          const img = document.getElementById(this.imgId)
+          const imgChild = img?.childNodes
+          if (imgChild?.length) img?.replaceChildren()
+          if (props.startAnimate) {
+            const path = document.createElementNS('http://www.w3.org/2000/svg', 'path')
+            path.setAttribute('d', this.path)
+            path.setAttribute('fill', 'none')
+            path.setAttribute('stroke', '#3498db')
+            path.setAttribute('stroke-width', '1')
+            path.setAttribute('stroke-dashoffset', '0')
+            const pathLength = Math.ceil(path?.getTotalLength())
+            path.setAttribute('stroke-dasharray', `0, ${pathLength}`)
+            const pathAnimate = document.createElementNS('http://www.w3.org/2000/svg', 'animate')
+            pathAnimate.setAttribute('attributeName', 'stroke-dasharray')
+            pathAnimate.setAttribute('begin', '0s')
+            pathAnimate.setAttribute('dur', `${this.duration}s`)
+            pathAnimate.setAttribute('values', `0, ${pathLength}; ${pathLength}, 0`)
+            pathAnimate.setAttribute('calcMode', 'linear')
+            pathAnimate.setAttribute('fill', 'freeze')
+            path.appendChild(pathAnimate)
+            g?.appendChild(path)
+  
+            // 给移动目标创建动画
+            const animateMotion = document.createElementNS('http://www.w3.org/2000/svg', 'animateMotion')
+            animateMotion.setAttribute('dur', `${this.duration}s`)
+            animateMotion.setAttribute('fill', 'freeze')
+            animateMotion.setAttribute('path', this.path)
+            img?.appendChild(animateMotion)
+            animateMotion.beginElement()
+            pathAnimate.beginElement()
+            emit('update:startAnimate', false)
+            setTimeout(() => {
+              path.style.display = 'none'
+            }, this.duration * 1000)
+          }
+        }
+      }
+      return obj
+    })
+    console.log(888, svgConfig, list.value)
+    if (timer.value) clearTimeout(timer.value)
+    timer.value = setTimeout(() => {
+      for (let k = 0; k < list.value.length; k++) {
+        const el = list.value[k]
+        el.callBack()
+      }
+    }, 200)
+  }
+  
+  // init
+  function initSVG() {
+    if (!props.menuItem || !props.menuItem.currentStake) {
+      svgConfig.tunnel = []
+      svgConfig.leng = []
+      window.$notification.warning({ content: `隧道数据配置错误${JSON.stringify(props.menuItem)}`, duration: 5000 })
+      return
+    }
+    svgConfig.width = props.menuItem!.length
+    svgConfig.tunnel = [
+      {
+        x: 0, y: 0, w: props.menuItem!.currentStake - props.menuItem!.startStake, h: 400, img: props.menuItem!.isIn ? img2 : img1
+      },
+      {
+        x: props.menuItem!.currentStake - props.menuItem!.startStake, y: 0, w: props.menuItem!.length - (props.menuItem!.currentStake - props.menuItem!.startStake), h: 400, img: props.menuItem!.isIn ? img1 : img2
+      }
+    ]
+    svgConfig.leng = [
+      { name: '隧道进口', stake: mileage2string(props.menuItem!.startStake, props.menuItem!.stakeType), x: '0%' },
+      { name: '当前掌子面', stake: mileage2string(props.menuItem!.currentStake, props.menuItem!.stakeType), x: `${(props.menuItem!.currentStake - props.menuItem!.startStake) / props.menuItem!.length * 100 - 2.5}%` },
+      { name: '隧道出口', stake: mileage2string(props.menuItem!.endStake, props.menuItem!.stakeType), x: `${props.menuItem!.length / props.menuItem!.length * 100 - 5}%` }
+    ]
+  }
+  
+  watch(() => props.menuItem, (v) => {
+    initSVG()
+  })
+  
+  watch(() => props.data, (v) => {
+    console.log(777, v)
+  
+    setData(v)
+  })
+  onMounted(() => clearTimeout(timer.value))
+  </script>
+  
+  <style lang="less" scoped>
+  .box {
+    width: 100%;
+    height: 100%;
+  
+    svg {
+      width: 100%;
+      height: calc(100% - 98px);
+      z-index: 1;
+    }
+  
+    &-leng {
+      position: relative;
+      width: 100%;
+      height: 98px;
+      left: 0;
+      bottom: 0;
+  
+      &-item {
+        position: absolute;
+        text-align: center;
+        font-size: 0.14rem;
+        top: 50%;
+        transform: translate(0, -50%);
+        &:nth-child(2){
+          top: 0;
+        }
+      }
+    }
+  }
+  </style>