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>
相关推荐
用户4611696572812 分钟前
网站 HTTPS 不稳定?证书排查指南
前端
n876524 分钟前
Playwright Java WEB UI 自动化实现框架与逻辑
java·前端·ui
keyipatience26 分钟前
TypeScript学习(1)
前端·javascript·学习·typescript·html·css3
xifangge20251 小时前
基于 JavaScript 实现 CNC 数控切削参数与螺纹公差纯前端核算引擎
开发语言·前端·javascript
wangruofeng1 小时前
2026 Web 技术选型指南,AI 时代如何选择适合项目的技术栈
前端·aigc·ai编程
honkun61 小时前
vue 表格组件 vxe-table 修改滚动条样式,修改滚动条样式,统一宽度、颜色与自动隐藏
前端·javascript·vue.js
爱勇宝2 小时前
研究数字孪生的这半年,做了个 Demo,也有了一些新想法
前端·程序员
不可能片场2 小时前
工程实践 一个缓存把选片从十分钟压到一分钟
前端·electron
前端小万2 小时前
探索副业五个月,最终以放弃告终
前端·后端·面试
FRing032 小时前
JavaScript入门到精通二十四(正则表达式、元字符、修饰符)
开发语言·前端·javascript·正则表达式