167 lines
4.3 KiB
Vue
167 lines
4.3 KiB
Vue
<template>
|
|
<canvas
|
|
ref="starsCanvas"
|
|
:class="cn('absolute inset-0 w-full h-full', $props.class)"
|
|
></canvas>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { cn } from "@/shadcn/lib/utils";
|
|
import { onMounted, ref } from "vue";
|
|
|
|
interface Star {
|
|
x: number;
|
|
y: number;
|
|
z: number;
|
|
speed: number;
|
|
}
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
color?: string;
|
|
count?: number;
|
|
class?: string;
|
|
}>(),
|
|
{
|
|
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({
|
|
x: (Math.random() - 0.5) * 2 * canvas.width,
|
|
y: (Math.random() - 0.5) * 2 * canvas.height,
|
|
z: Math.random() * canvas.width,
|
|
speed: Math.random() * 5 + 2, // Speed for falling effect
|
|
});
|
|
}
|
|
|
|
animate(); // Start animation
|
|
});
|
|
|
|
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
|
|
.split("")
|
|
.map((char) => char + char)
|
|
.join("");
|
|
}
|
|
|
|
// Parse the r, g, b values from the hex string
|
|
const bigint = 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 {
|
|
r,
|
|
g,
|
|
b,
|
|
};
|
|
}
|
|
|
|
// 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 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
|
|
|
|
stars.forEach((star) => {
|
|
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
|
|
}
|
|
|
|
// Set canvas to full screen
|
|
function resizeCanvas() {
|
|
const canvas = starsCanvas.value;
|
|
if (!canvas) return;
|
|
|
|
canvas.width = canvas.clientWidth;
|
|
canvas.height = canvas.clientHeight;
|
|
}
|
|
</script>
|