feat: 添加 InspiraUI 组件
This commit is contained in:
160
src/pages/UI-components/InspiraUI/FallingStarsBg.vue
Normal file
160
src/pages/UI-components/InspiraUI/FallingStarsBg.vue
Normal file
@ -0,0 +1,160 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/shadcn/lib/utils';
|
||||
import { onMounted, ref } from 'vue';
|
||||
|
||||
interface Star {
|
||||
speed: number;
|
||||
x: number;
|
||||
y: number;
|
||||
z: number;
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
class?: string;
|
||||
color?: string;
|
||||
count?: number;
|
||||
}>(),
|
||||
{
|
||||
color: '#FFF',
|
||||
count: 200,
|
||||
},
|
||||
);
|
||||
|
||||
const starsCanvas = ref<HTMLCanvasElement | null>(null);
|
||||
let perspective: number = 0;
|
||||
let stars: Star[] = [];
|
||||
let ctx: CanvasRenderingContext2D | null = null;
|
||||
|
||||
onMounted(() => {
|
||||
const canvas = starsCanvas.value;
|
||||
if (!canvas) return;
|
||||
|
||||
window.addEventListener('resize', resizeCanvas);
|
||||
resizeCanvas(); // Call it initially to set correct size
|
||||
|
||||
perspective = canvas.width / 2;
|
||||
stars = [];
|
||||
|
||||
// Initialize stars
|
||||
for (let i = 0; i < props.count; i++) {
|
||||
stars.push({
|
||||
speed: Math.random() * 5 + 2, // Speed for falling effect
|
||||
x: (Math.random() - 0.5) * 2 * canvas.width,
|
||||
y: (Math.random() - 0.5) * 2 * canvas.height,
|
||||
z: Math.random() * canvas.width,
|
||||
});
|
||||
}
|
||||
|
||||
animate(); // Start animation
|
||||
});
|
||||
|
||||
// Function to animate the stars
|
||||
function animate() {
|
||||
const canvas = starsCanvas.value;
|
||||
if (!canvas) return;
|
||||
|
||||
ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear canvas for each frame
|
||||
|
||||
for (const star of stars) {
|
||||
drawStar(star);
|
||||
|
||||
// Move star towards the screen (decrease z)
|
||||
star.z -= star.speed;
|
||||
|
||||
// Reset star when it reaches the viewer (z = 0)
|
||||
if (star.z <= 0) {
|
||||
star.z = canvas.width;
|
||||
star.x = (Math.random() - 0.5) * 2 * canvas.width;
|
||||
star.y = (Math.random() - 0.5) * 2 * canvas.height;
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(animate); // Continue animation
|
||||
}
|
||||
|
||||
// Function to draw a star with a sharp line and blurred trail
|
||||
function drawStar(star: Star) {
|
||||
const canvas = starsCanvas.value;
|
||||
if (!canvas) return;
|
||||
|
||||
ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
const scale = perspective / (perspective + star.z); // 3D perspective scale
|
||||
const x2d = canvas.width / 2 + star.x * scale;
|
||||
const y2d = canvas.height / 2 + star.y * scale;
|
||||
const size = Math.max(scale * 3, 0.5); // Size based on perspective
|
||||
|
||||
// Previous position for a trail effect
|
||||
const prevScale = perspective / (perspective + star.z + star.speed * 15); // Longer trail distance
|
||||
const xPrev = canvas.width / 2 + star.x * prevScale;
|
||||
const yPrev = canvas.height / 2 + star.y * prevScale;
|
||||
|
||||
const rgb = hexToRgb();
|
||||
|
||||
// Draw blurred trail (longer, with low opacity)
|
||||
ctx.save(); // Save current context state for restoring later
|
||||
ctx.strokeStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.2)`;
|
||||
ctx.lineWidth = size * 2.5; // Thicker trail for a blur effect
|
||||
ctx.shadowBlur = 35; // Add blur to the trail
|
||||
ctx.shadowColor = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.8)`;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x2d, y2d);
|
||||
ctx.lineTo(xPrev, yPrev); // Longer trail
|
||||
ctx.stroke();
|
||||
ctx.restore(); // Restore context state to remove blur from the main line
|
||||
|
||||
// Draw sharp line (no blur)
|
||||
ctx.strokeStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.6)`;
|
||||
ctx.lineWidth = size; // The line width is the same as the star's size
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x2d, y2d);
|
||||
ctx.lineTo(xPrev, yPrev); // Sharp trail
|
||||
ctx.stroke();
|
||||
|
||||
// Draw the actual star (dot)
|
||||
ctx.fillStyle = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 1)`;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x2d, y2d, size / 4, 0, Math.PI * 2); // Dot with size matching the width
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
function hexToRgb() {
|
||||
let hex = props.color.replace(/^#/, '');
|
||||
|
||||
// If the hex code is 3 characters, expand it to 6 characters
|
||||
if (hex.length === 3) {
|
||||
hex = [...hex].map((char) => char + char).join('');
|
||||
}
|
||||
|
||||
// Parse the r, g, b values from the hex string
|
||||
const bigint = Number.parseInt(hex, 16);
|
||||
const r = (bigint >> 16) & 255; // Extract the red component
|
||||
const g = (bigint >> 8) & 255; // Extract the green component
|
||||
const b = bigint & 255; // Extract the blue component
|
||||
|
||||
// Return the RGB values as a string separated by spaces
|
||||
return {
|
||||
b,
|
||||
g,
|
||||
r,
|
||||
};
|
||||
}
|
||||
|
||||
// Set canvas to full screen
|
||||
function resizeCanvas() {
|
||||
const canvas = starsCanvas.value;
|
||||
if (!canvas) return;
|
||||
|
||||
canvas.width = canvas.clientWidth;
|
||||
canvas.height = canvas.clientHeight;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<canvas ref="starsCanvas" :class="cn('absolute inset-0 w-full h-full', $props.class)"></canvas>
|
||||
</template>
|
Reference in New Issue
Block a user