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>
相关推荐
༄沐࿆风࿆࿆19 分钟前
JavaScript函数完全指南:从基础语法到闭包原理与应用实战
开发语言·javascript
Geek-Chow40 分钟前
微服务认证与授权:01 — 概念
java·前端·数据库
finyouIT1 小时前
锚点实现了点击导航平滑滚动到页面指定位置的三种方法
javascript·css
狂炫冰美式1 小时前
凌晨睡不着,我给台风巴威写了个追踪网站
前端·后端·github
雪隐2 小时前
用Flutter做背单词APP-01服务器?不存在的,我家里就有
前端·人工智能·后端
用户2204603958682 小时前
ES模块和commonJS两种标准下中实现__dirname?一篇文章教你理清楚
前端
犹豫的大炮3 小时前
jQuery最核心的基础设施之一——数据缓存模块进化史
前端·缓存·jquery
小碗细面3 小时前
从 PRD 到上线:SpecKit 如何让 AI 真正参与前端交付
前端·ai编程·claude
WL学习笔记3 小时前
链式队列(数据结构)
前端·数据结构·jquery
我是大卫3 小时前
React源码解析-第三部分:协调与提交
前端·react.js·源码