bunny-admin/src/components/ParticleClock/index.vue

194 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="particle-clock-container">
<canvas ref="canvas"></canvas>
</div>
</template>
<script setup lang="ts" name="ParticleClock">
import { ref, onMounted, onUnmounted, computed } from "vue";
import { useGlobalStore } from "@/stores/modules/global";
const globalStore = useGlobalStore();
const primary = computed(() => globalStore.primary);
onMounted(() => {
// 当组件挂载时
ctx = canvas.value.getContext("2d", {
// 获取 2D 上下文
willReadFrequently: true
});
initCanvasSize(); // 初始化画布尺寸
draw(); // 开始绘制
});
onUnmounted(() => {
// 当组件卸载时
cancelAnimationFrame(animationFrameId); // 取消动画帧
});
/* 创建一个画布引用 */
const canvas = ref();
/* 定义画布上下文变量 */
let ctx: any;
/* 定义动画帧 ID 变量 */
let animationFrameId: any;
/* 初始化画布尺寸函数 */
const initCanvasSize = () => {
// 设置画布宽度
canvas.value.width = window.innerWidth * devicePixelRatio;
// 设置画布高度
canvas.value.height = window.innerHeight * devicePixelRatio;
};
/* 获取 [min, max] 范围内的随机整数 */
const getRandom = (min: number, max: number) => {
// 返回随机整数
return Math.floor(Math.random() * (max + 1 - min) + min);
};
/* 粒子类 */
class Particle {
x: number;
y: number;
size: number;
// 构造函数
constructor() {
const r = Math.min(canvas.value.width, canvas.value.height) / 2; // 计算半径
const cx = canvas.value.width / 2; // 计算中心点 X 坐标
const cy = canvas.value.height / 2; // 计算中心点 Y 坐标
const rad = (getRandom(0, 360) * Math.PI) / 180; // 计算随机弧度
this.x = cx + r * Math.cos(rad); // 设置粒子 X 坐标
this.y = cy + r * Math.sin(rad); // 设置粒子 Y 坐标
this.size = 12; // 设置粒子大小
}
// 绘制粒子方法
draw() {
ctx.beginPath(); // 开始路径
ctx.fillStyle = primary.value; // 设置填充样式
ctx.arc(this.x, this.y, this.size, 0, 2 * Math.PI); // 绘制圆形
ctx.fill(); // 填充路径
}
// 移动粒子方法
moveTo(tx: number, ty: number) {
const duration = 500; // 500ms的运动时间
const sx = this.x, // 起始 X 坐标
sy = this.y; // 起始 Y 坐标
const xSpeed = (tx - sx) / duration; // 计算 X 方向速度
const ySpeed = (ty - sy) / duration; // 计算 Y 方向速度
const startTime = Date.now(); // 记录开始时间
const _move = () => {
// 移动函数
const t = Date.now() - startTime; // 计算已过时间
const x = sx + xSpeed * t; // 计算当前 X 坐标
const y = sy + ySpeed * t; // 计算当前 Y 坐标
this.x = x; // 更新粒子 X 坐标
this.y = y; // 更新粒子 Y 坐标
if (t >= duration) {
// 如果已达到运动时间
this.x = tx; // 设置粒子 X 坐标为目标 X 坐标
this.y = ty; // 设置粒子 Y 坐标为目标 Y 坐标
return; // 结束函数
}
// xy改动一点
requestAnimationFrame(_move); // 请求下一帧动画
};
_move(); // 开始移动
}
}
/* 粒子数组 */
const partciles: any[] = [];
/* 文本变量 */
let text: any = null;
/* 清除画布函数 */
const clear = () => {
ctx.clearRect(0, 0, canvas.value.width, canvas.value.height); // 清除画布内容
};
/* 绘制函数 */
const draw = () => {
clear(); // 清除画布
update(); // 更新状态
partciles.forEach(p => p.draw()); // 绘制每个粒子
animationFrameId = requestAnimationFrame(draw); // 请求下一帧动画
};
/* 获取文本函数 */
const getText = () => {
// 返回当前时间字符串
return new Date().toTimeString().substring(0, 8);
};
/* 更新状态函数 */
const update = () => {
const newText = getText(); // 获取新文本
if (newText === text) {
// 如果文本没有变化
return; // 结束函数
}
clear(); // 清除画布
text = newText; // 更新文本
// 画文本
const { width, height } = canvas.value; // 获取画布宽高
ctx.fillStyle = "#000"; // 设置填充样式
ctx.textBaseline = "middle"; // 设置文本基线
ctx.font = `${240 * devicePixelRatio}px 'DS-Digital', sans-serif`; // 设置字体样式
ctx.fillText(text, (width - ctx.measureText(text).width) / 2, height / 2); // 绘制文本
const points = getPoints(); // 获取点集
clear(); // 清除画布
for (let i = 0; i < points.length; i++) {
// 遍历点集
let p = partciles[i]; // 获取粒子
if (!p) {
// 如果粒子不存在
p = new Particle(); // 创建新粒子
partciles.push(p); // 将粒子添加到数组
}
const [x, y] = points[i]; // 获取点坐标
p.moveTo(x, y); // 移动粒子到目标位置
}
if (points.length < partciles.length) {
// 如果点集数量小于粒子数量
partciles.splice(points.length); // 删除多余粒子
}
};
/* 获取点集函数 */
const getPoints = () => {
const { width, height, data } = ctx.getImageData(
// 获取画布图像数据
0,
0,
canvas.value.width,
canvas.value.height
);
const points = []; // 点集数组
const gap = 6; // 间隔
for (let i = 0; i < width; i += gap) {
// 遍历宽度
for (let j = 0; j < height; j += gap) {
// 遍历高度
const index = (i + j * width) * 4; // 计算像素索引
const r = data[index]; // 获取红色通道值
const g = data[index + 1]; // 获取绿色通道值
const b = data[index + 2]; // 获取蓝色通道值
const a = data[index + 3]; // 获取透明度通道值
if (r === 0 && g === 0 && b === 0 && a === 255) {
// 如果像素为黑色
points.push([i, j]); // 将坐标添加到点集
}
}
}
return points; // 返回点集
};
</script>
<style scoped lang="less">
.particle-clock-container {
width: 120px;
height: 50px;
margin-top: 4px;
margin-right: 6px;
canvas {
display: block;
width: 100%;
height: 100%;
background: #ffffff;
}
}
</style>