<script setup>
|
import { ref, computed } from "vue";
|
|
const props = defineProps({
|
offset: {
|
type: Array,
|
default: () => [0, 0],
|
},
|
/**
|
* 每一段路径一个对象
|
* {
|
* points: [],
|
* 点之前是否需要圆角 个数比points少两个 圆角为true
|
* roundCornerFlags: [],
|
* }
|
*/
|
lineList: {
|
type: Array,
|
default: () => [],
|
},
|
color: {
|
type: String,
|
// default: "#67aa57",
|
default: "#fff",
|
},
|
lineWidth: {
|
type: Number,
|
default: 16,
|
},
|
r: {
|
type: [Number, String],
|
default: 6
|
},
|
});
|
|
function calcVectorProperties(A, B) {
|
let [x0, y0] = A;
|
let [x1, y1] = B;
|
// 计算向量分量
|
const dx = x1 - x0;
|
const dy = y1 - y0;
|
|
// 计算向量的模长(欧几里得距离)
|
const magnitude = Math.sqrt(dx * dx + dy * dy);
|
if (magnitude < props.r) {
|
throw new Error('两点之间距离过近');
|
}
|
|
// 处理零向量情况(避免除以零)
|
const normalizedVector = magnitude === 0
|
? [0, 0]
|
: [ dx / magnitude, dy / magnitude ];
|
|
// 计算向量与x轴正方向的夹角(弧度)
|
// Math.atan2 返回值范围:[-π, π]
|
const angleRadians = Math.atan2(normalizedVector[1], normalizedVector[0]);
|
|
// AB上离B点的距离r距离的点的坐标(x2, y2)
|
const x2 = x1 - props.r * normalizedVector[0];
|
const y2 = y1 - props.r * normalizedVector[1];
|
|
return {
|
x2,
|
y2
|
};
|
}
|
|
const pathList = computed(() => {
|
let list = props.lineList;
|
let res = [];
|
for (let i = 0, len = list.length; i < len; i++) {
|
let { points, roundCornerFlags, lineColor } = list[i];
|
let path = `M ${points[0].join(",")} `;
|
for (let j = 1, len2 = points.length; j < len2; j++) {
|
let str = '';
|
let p0 = points[j - 1],
|
p1 = points[j],
|
p2 = points[j + 1];
|
|
if (j < len2 - 1 && roundCornerFlags[j - 1]) {
|
let { x2, y2 } = calcVectorProperties(p0, p1);
|
let { x2: x3, y2: y3 } = calcVectorProperties(p2, p1);
|
str = ` L ${x2},${y2} Q ${p1.join(",")} ${x3},${y3} `;
|
} else {
|
str = ` L ${points[j].join(",")} `
|
}
|
path += str;
|
}
|
res.push({path, lineColor});
|
}
|
return res;
|
});
|
|
</script>
|
|
<template>
|
<g ref="g" :transform="'translate(' + offset.join(',') + ')'">
|
<path v-for="(item, index) in pathList" :key="'path0_' + index"
|
fill="none"
|
:d="item['path']"
|
:stroke-width="2"
|
:stroke="item['lineColor']"
|
stroke-linecap="round"
|
stroke-linejoin="round"
|
>
|
<animate attributeName="stroke-width" values="2;8;2" dur="2s" repeatCount="indefinite" />
|
<animate attributeName="opacity" values="1;0.4;1" dur="2s" repeatCount="indefinite" />
|
</path>
|
<path v-for="(item, index) in pathList" :key="'path1_' + index"
|
fill="none"
|
:d="item['path']"
|
:stroke-width="lineWidth - 4"
|
stroke="rgba(255,255,255, 0.4)"
|
stroke-linecap="round"
|
stroke-linejoin="round"
|
stroke-dasharray="18 142"
|
stroke-dashoffset="174"
|
>
|
<animate attributeName="stroke-dashoffset" from="174" to="14" dur="4s" repeatCount="indefinite" />
|
<animate attributeName="stroke-width" :values="`${lineWidth - 8};${lineWidth + 4};${lineWidth - 8}`" dur="2s" repeatCount="indefinite" />
|
</path>
|
<path v-for="(item, index) in pathList" :key="'path2_' + index"
|
:d="item['path']" :stroke="color" :stroke-width="lineWidth"
|
fill="none"
|
stroke-linecap="round"
|
stroke-linejoin="round"
|
stroke-dasharray="4 156"
|
stroke-dashoffset="160">
|
<animate attributeName="stroke-dashoffset" from="160" to="0" dur="4s" repeatCount="indefinite" />
|
<animate attributeName="stroke-width" :values="`${lineWidth - 8};${lineWidth + 8};${lineWidth - 8}`" dur="2s" repeatCount="indefinite" />
|
</path>
|
</g>
|
</template>
|