SVG 实现飞线功能

svg 实现如图飞功能,具体代码如下:

vue 中使用

html 复制代码
<template>
  <div>
   <span id="dv-1">测试</span>
   
   <span id="dv-2" style="margin:100px;">测试</span>
  </div>
  <fly-line2 :fly-list="flyLineList"></fly-line2>
</template>

<script lang="ts">

import Flyline2 from '@/components/FlylineChartEnhanced/FlyLine2.vue'
import {type FlyLineItem } from '@/api/types/RespTypes'

export default {
  name: 'home',
  components: {
    Flyline2
  },
  setup() {
  
    const flyLineList = ref<FlyLineItem[]>([])

    function getCenter(el: HTMLElement) {
      const rect = el.getBoundingClientRect()
      return {
        x: rect.left + rect.width / 2,
        y: rect.top + rect.height / 2
      }
    }

    function test(){
      nextTick(()=>{
        const el  = document.getElementById("dv-1")
        const el2 = document.getElementById("dv-2")
        const form = getCenter(el)
        const to = getCenter(el2)
        flyLineList.value.push({
          id: "line_1",
          from: [form.x, form.y],
          to: [to.x, to.y]
        })
      })
    }

    onMounted(() => {
      test()
    })

    return {
      flyLineList
    }
  },
}
</script>

<style lang="scss" scoped>

</style>

组件 FlyLine2.vue

html 复制代码
<template>
  <svg  class="svg-fly-line" width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">
    <defs>
      <radialGradient id="lineGradient" cx="50%" cy="50%" r="50%">
        <stop offset="0%" stop-color="#fff" stop-opacity="1"></stop>
        <stop offset="100%" stop-color="#fff" stop-opacity="0"></stop>
      </radialGradient>

      <linearGradient id="lineGradient1" x1="91.93%" y1="77.23%" x2="8.07%" y2="22.77%">
        <!-- 12.37% 处为透明蓝色 -->
        <stop offset="12.37%" stop-color="#00A0E9" stop-opacity="0" />
        <!-- 86.15% 处为不透明蓝色 -->
        <stop offset="86.15%" stop-color="#00A0E9" stop-opacity="1" />
      </linearGradient>

    </defs>

    <!-- 遍历多条飞线路径 -->
    <g v-for="(item, index) in lineLst" :key="index" :data-index="index">
      <!-- 动态路径 -->
      <path :id="`flyPath${index}`" :d="getFlyPath(item)" fill="transparent"></path>
      <!-- 遮罩 -->
      <mask :id="`flyMask${index}`">
        <!-- 拖尾 r 长度  -->
        <circle cx="0" cy="0" r="60" fill="url(#lineGradient)">
          <animateMotion :dur="`${item.speed || 5000}ms`" :path="getFlyPath(item)" rotate="auto" repeatCount="indefinite"></animateMotion>
        </circle>
      </mask>
      <!-- 底层轨迹线 -->
      <use :xlink:href="`#flyPath${index}`" stroke-width="8" stroke="rgba(255, 255, 255, .1)"></use>
      <!-- 渐变流光拖尾 → from/to 自动计算 -->
      <use :xlink:href="`#flyPath${index}`" stroke-width="6" stroke="#00A0E9" :mask="`url(#flyMask${index})`">
        <animate
          attributeName="stroke-dasharray"
          :from="`0, ${item.pathLength}`"
          :to="`${item.pathLength}, 0`"
          :dur="`${item.speed || 5000}ms`"
          repeatCount="indefinite"
        ></animate>
      </use>

      <!-- 箭头 -->
      <path d="M0,-4 L8,0 L0,4 Z" fill="#00A0E9">
        <animateMotion
          :dur="`${item.speed || 5000}ms`"
          :path="getFlyPath(item)"
          rotate="auto"
          repeatCount="indefinite"
        />
      </path>
    </g>
  </svg>
</template>

<script setup lang="ts">
import { defineProps ,getCurrentInstance} from 'vue'

// 单条飞线类型
export interface FlyLineItem {
  from: number[] // 起点 x,y坐标
  to: number[] // 终点 x
  curve?: number // 弧线偏移 正负控制上下弯曲
  lineColor?: string // 轨迹颜色
  lineWidth?: number // 轨迹宽度
  arrowFill?: string // 箭头填充色
  arrowScale?: number // 箭头缩放大小
  speed?: number // 飞行时长 ms
  pathLength?:number
}

//@ts-ignore
const { ctx: that } = getCurrentInstance()

const props = defineProps<{
  flyList: FlyLineItem[]
}>()

const lineLst =ref<FlyLineItem[]>([])

watch(()=>props.flyList,async (nVal,oVal)=>{
  await nextTick()
  lineLst.value = props.flyList
  lineLst.value.forEach((item, index) => {
    const path = document.getElementById(`flyPath${index}`)
    if (path) {
      //@ts-ignore
      const length= path.getTotalLength()
      console.log('[FlyLine2#getPathLength:72]:', `${length}`)
      item.pathLength = length
    }
  })
},{deep:true,immediate:true})

// 生成贝塞尔曲线路径
const getFlyPath = (item: FlyLineItem) => {
  const { from, to, curve = -70 } = item
  const centerX = (from[0] + to[0]) / 2
  const centerY = (from[1] + to[1]) / 2 + curve
  //d="M 起点X 起点Y Q 控制点X 控制点Y 终点X 终点Y"
  return `M${from[0]},${from[1]} Q${centerX},${centerY} ${to[0]},${to[1]}`
}

async function getPathLength(idx: number) {
  await nextTick()
  //@ts-ignore
  const path:SVGPathElement|null = document.getElementById(`flyPath${idx}`)
  if (path) {
    const length= path.getTotalLength()
    console.log('[FlyLine2#getPathLength:108]:', `${length}`)
    return length
  }
  return 0
}

// 生成贝塞尔路径
// function getPathD(p1: { x: number, y: number }, p2: { x: number, y: number }, curveOffset = -80) {
//   const mx = (p1.x + p2.x) / 2
//   const my = (p1.y + p2.y) / 2 + curveOffset
//   return `M ${p1.x} ${p1.y} Q ${mx} ${my} ${p2.x} ${p2.y}`
// }

// 获取元素中心点
function getCenter(el: HTMLElement) {
  const rect = el.getBoundingClientRect()
  return {
    x: rect.left + rect.width / 2,
    y: rect.top + rect.height / 2,
  }
}

function handleResize() {
  lineLst.value.forEach((item, index) => {
    //@ts-ignore
    const path:SVGPathElement|null = document.getElementById(`flyPath${index}`)
    if (path) {
      const length= path.getTotalLength()
      item.pathLength = length
    }
  })
  that.$forceUpdate()
}

// 挂载
onMounted(() => {
  window.addEventListener('resize', handleResize)
})
// 销毁
onUnmounted(() => {
  window.removeEventListener('resize', handleResize)
})
</script>

<style scoped lang="scss">
.svg-fly-line {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
  z-index: 1;
}
</style>
相关推荐
yuanyxh1 天前
Mac 软件推荐
前端·javascript·程序员
万少1 天前
AtomCode开发微信小程序《谁去呀》 全流程
前端·javascript·后端
某人辛木1 天前
Web自动化测试
前端·python·pycharm·pytest
Kagol1 天前
Superpowers GSD gstack AgentSkills深度测评
前端·人工智能
excel1 天前
JavaScript 字符串与模板字面量:从表象到本质理解
前端
京东云开发者1 天前
当AI成为导演-如何用AI创作动漫短剧
前端
李白的天不白1 天前
使用 SmartAdmin 进行前后端开发
java·前端
乘风gg1 天前
🤡PUA AI Coding 工具 的 10 条终极语录
前端·ai编程·claude
学Linux的语莫1 天前
Vue 3 入门教程
前端·javascript·vue.js
怕浪猫1 天前
第一章、Chrome DevTools Protocol (CDP) 详解
前端·javascript·chrome