Skip to content

Commit e6c5954

Browse files
committed
Created matrix rain file and logic
1 parent 1fe300b commit e6c5954

1 file changed

Lines changed: 103 additions & 0 deletions

File tree

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<script setup lang="ts">
2+
import { onMounted, onUnmounted, ref } from 'vue';
3+
4+
const canvasRef = ref<HTMLCanvasElement | null>(null);
5+
6+
const CHARS = '01アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン';
7+
const FONT_SIZE = 14;
8+
const COLUMN_SPACING = 3; // use every Nth column for fewer streams
9+
const DROP_SPEED = 1.2; // pixels per frame (lower = slower)
10+
const TRAIL_LENGTH = 12; // characters per column (longer lines)
11+
let animationId = 0;
12+
let columns: number[] = [];
13+
let resizeObserver: ResizeObserver | null = null;
14+
15+
function initMatrix(canvas: HTMLCanvasElement) {
16+
const ctx = canvas.getContext('2d');
17+
if (!ctx) return;
18+
19+
const dpr = Math.min(window.devicePixelRatio ?? 1, 2);
20+
const w = canvas.clientWidth;
21+
const h = canvas.clientHeight;
22+
23+
canvas.width = w * dpr;
24+
canvas.height = h * dpr;
25+
ctx.scale(dpr, dpr);
26+
27+
const colCount = Math.floor(w / FONT_SIZE / COLUMN_SPACING);
28+
columns = Array.from({ length: colCount }, () => Math.random() * h);
29+
}
30+
31+
function draw(canvas: HTMLCanvasElement) {
32+
const ctx = canvas.getContext('2d');
33+
if (!ctx) return;
34+
35+
const w = canvas.clientWidth;
36+
const h = canvas.clientHeight;
37+
38+
ctx.fillStyle = 'rgba(0, 0, 0, 0.06)';
39+
ctx.fillRect(0, 0, w, h);
40+
41+
ctx.font = `${FONT_SIZE}px "Geist Mono", monospace`;
42+
43+
for (let i = 0; i < columns.length; i++) {
44+
const y = columns[i];
45+
const x = i * FONT_SIZE * COLUMN_SPACING;
46+
47+
// Draw longer line: head bright, then fading trail upward
48+
for (let j = 0; j < TRAIL_LENGTH; j++) {
49+
const trailY = y - j * FONT_SIZE;
50+
if (trailY < -FONT_SIZE) break;
51+
const opacity = j === 0 ? 1 : Math.max(0.02, 0.5 - j * 0.04);
52+
ctx.fillStyle = `rgba(34, 197, 94, ${opacity})`;
53+
ctx.fillText(
54+
CHARS[Math.floor(Math.random() * CHARS.length)],
55+
x,
56+
trailY,
57+
);
58+
}
59+
60+
columns[i] =
61+
y > h + Math.random() * 50 ? 0 : y + DROP_SPEED;
62+
}
63+
64+
animationId = requestAnimationFrame(() => draw(canvas));
65+
}
66+
67+
function start(canvas: HTMLCanvasElement) {
68+
initMatrix(canvas);
69+
draw(canvas);
70+
}
71+
72+
function stop() {
73+
if (animationId) cancelAnimationFrame(animationId);
74+
}
75+
76+
onMounted(() => {
77+
const canvas = canvasRef.value;
78+
const container = canvas?.parentElement;
79+
if (!canvas || !container) return;
80+
81+
start(canvas);
82+
83+
resizeObserver = new ResizeObserver(() => {
84+
if (!canvasRef.value) return;
85+
stop();
86+
start(canvasRef.value);
87+
});
88+
resizeObserver.observe(container);
89+
});
90+
91+
onUnmounted(() => {
92+
stop();
93+
resizeObserver?.disconnect();
94+
});
95+
</script>
96+
97+
<template>
98+
<canvas
99+
ref="canvasRef"
100+
class="absolute inset-0 h-full w-full"
101+
aria-hidden="true"
102+
/>
103+
</template>

0 commit comments

Comments
 (0)