app.vue
<template>
<div class="View" ref="View">
<div class="Layout" :style="Layout">
<Block v-for="item in blockData" :data="item" />
div>
<svg class="Layout" :style="Layout">
<Line v-for="item in LinkData" :data="item" :count="XCounts" />
svg>
div>
template>
<script setup>
import { onMounted, ref } from 'vue';
import Block from './Block.vue';
import Line from './Line.vue'
const View = ref();
// 随机函数
const Layout = ref({});
const random = (min, max) => min + Math.floor(Math.random() * (max - min + 1));
const blockData = ref([]);
const LinkData = ref([]);
const XCounts = ref(0);
// 生成测试数据
onMounted(() => {
var XLength = parseInt(View.value.clientWidth / 100);
var Ylength = parseInt(View.value.clientHeight / 100);
// console.log(XLength);
XCounts.value = XLength;
var AllCounts = XLength * Ylength;
Layout.value.width = XLength * 100 + 'px';
Layout.value.height = Ylength * 100 + 'px';
Layout.value.left = (View.value.clientWidth - XLength * 100) / 2 + 'px';
Layout.value.top = (View.value.clientHeight - Ylength * 100) / 2 + 'px';
// 生成测试数据
for (let i = 0; i < AllCounts; i++) {
var Data = {
isLink: Math.random() < 0.333,
i: i,
};
blockData.value.push(Data);
if (Data.isLink) {
LinkData.value.push(Data);
}
};
var ActiveIndex = random(0, LinkData.value.length - 1)
for (let i = 0; i < LinkData.value.length; i++) {
const element = LinkData.value[i];
element.linkTo = LinkData.value[ActiveIndex].i;
}
})
script>
<style lang="less" scoped>
.View {
width: 100%;
height: 100%;
position: relative;
}
.Layout {
position: absolute;
}
svg.Layout {
pointer-events: none;
}
style>
Block.vue
<template>
<div class="Block" :class="{ 'HasAni': data.isLink }" @mouseenter="Play">
<environment-outlined v-if="data.isLink" class="Icon" ref="icon" />
div>
template>
<script setup>
import { EnvironmentOutlined } from '@ant-design/icons-vue'
import anime from 'animejs'
import { onMounted, onUnmounted, ref } from 'vue';
const props = defineProps({
data: Object
})
const icon = ref();
let AniObj = null;
onMounted(() => {
// 创建一个动画(其实也可以中关键帧实现,原理是一样的)
AniObj = anime({
targets: icon.value,
keyframes: [
{ rotateY: 0 },
{ translateY: -15, rotateY: 180 },
{ translateY: 0, rotateY: 360 },
],
easing: 'linear',
// 持续时间
duration: 1000,
autoplay: false,
})
})
// 鼠标进入时,中心播放动画
// 用 CSS 实现时,就是删除动画的 Class,间隔 1毫秒, 再添加这个Class就可以了
const Play = () => {
AniObj.restart();
}
onUnmounted(() => {
// 目前 Anime 没有释放缓存的办法,
// 只能停止动画
AniObj.pause();
})
script>
<style lang="less" scoped>
.Block {
width: 100px;
height: 100px;
float: left;
background-color: rgba(127, 255, 212, 0.1);
text-align: center;
line-height: 85px;
position: relative;
}
.HasAni {
transition: box-shadow 0.3s, background-color 0.2s;
}
.HasAni:hover {
box-shadow: inset 0 0 10px rgba(0, 255, 255, 0.3);
background-color: rgba(127, 255, 212, 0.2);
}
.Icon {
position: relative;
z-index: 2;
font-size: 25px;
color: rgb(27, 141, 216);
transform: translateY(0px) rotateY(0deg);
}
style>
Line.vue
<template>
<path ref="path" :style="{ 'animation-delay': delayTime + 's' }"
:d="`M${Start[0]} ${Start[1]} S${Control[0]} ${Control[1]} ${End[0]} ${End[1]}`" />
template>
<script setup>
import { ref } from 'vue';
import { computePosition, ControlPointCompute } from './Uint.js'
const props = defineProps({
data: Object,
count: Number
})
var delayTime = Math.random();
var { Form, To } = computePosition(props.data.linkTo, props.data.i, props.count);
const Start = ref(Form);
const End = ref(To);
const Control = ref(ControlPointCompute(Form, To));
script>
<style lang="less" scoped>
path {
stroke: rgba(255, 0, 0, 0.5);
stroke-width: 3;
fill: none;
stroke-width: 4;
stroke-dasharray: 500, 500;
animation: dash linear infinite 3s;
}
@keyframes dash {
to {
stroke-dashoffset: -1000;
}
}
style>
Uint.js
// 格式化传入点的信息
export var computePosition = (Form, to, count) => {
return {
Form: [Form % count * 100 + 50, parseInt(Form / count) * 100 + 50],
To: [to % count * 100 + 50, parseInt(to / count) * 100 + 50],
}
}
// 弧度值转角度值的常量
const R2A = 180 / Math.PI;
// 角度值转弧度值的常量
const A2R = (2 * Math.PI) / 360
const ConstantAngle = 45;
export var ControlPointCompute = (X, Y) => {
// X轴坐标点差值
const DX = Y[0] - X[0];
// Y轴坐标点差值
const DY = Y[1] - X[1];
// 鼠标位置与原点的弧度值
const radian = Math.atan2(DY, DX);
// 鼠标点与坐标点的 余弦 值
const CosRadian = Math.cos(radian);
// 计算控制点与原点的距离
const dis = Math.sqrt(Math.pow(Math.abs(DX), 2) + Math.pow(Math.abs(DY), 2)) / (2 - Math.abs(CosRadian) * (1 - 0.414));
// 计算控制点的弧度值
const ControlRadian = A2R * (radian * R2A - (CosRadian * ConstantAngle));
// 计算控制点位置
return [Math.cos(ControlRadian) * dis + X[0], Math.sin(ControlRadian) * dis + X[1]];
}