Skip to content

Commit 77c691a

Browse files
Add BackgroundDots component for animated background effects and integrate into Home page
1 parent a1d1e4c commit 77c691a

2 files changed

Lines changed: 166 additions & 5 deletions

File tree

app/[locale]/page.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import ScrollDiv from "../components/navigation/ScrollDiv";
1717
import { getAllPosts } from "../lib/api";
1818
import MovingDots from "../components/Canvas/MovingDots";
1919
import { getTranslations, setRequestLocale } from "next-intl/server";
20+
import BackgroundDots from "../components/BgMovingDots";
2021
export default async function Home({ params }) {
2122
// 1. Obtener idioma
2223
const { locale } = await params;
@@ -25,6 +26,7 @@ export default async function Home({ params }) {
2526
return (
2627
<PageContainer>
2728
<ScrollDiv />
29+
<BackgroundDots numDots={60} />
2830
<MainPageBg>
2931
<HomePageCover>
3032
<HomePageCoverText>
@@ -36,11 +38,12 @@ export default async function Home({ params }) {
3638
{/* <P5Sketch width={600} height={600} /> */}
3739
{/* <RandomPointCloud /> */}
3840
{/* <RandomDots numDots={200} width={800} height={400} /> */}
39-
<HomePageCoverImage>
40-
{/* <MovingDots numDots={100} width={100} height={150} speed={2} /> */}
41-
<MovingDots numDots={100} speed={2} />
42-
{/* <CircleBounce /> */}
43-
</HomePageCoverImage>
41+
{/* <HomePageCoverImage> */}
42+
<BackgroundDots numDots={40} />
43+
{/* <MovingDots numDots={100} width={100} height={150} speed={2} /> */}
44+
{/* <MovingDots numDots={100} speed={2} /> */}
45+
{/* <CircleBounce /> */}
46+
{/* </HomePageCoverImage> */}
4447
</HomePageCover>
4548
<MdParagraph>
4649
Este sitio web está en construcción, pero pronto será un lugar donde

app/components/BgMovingDots.js

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
"use client";
2+
import React, { useEffect, useRef, useState } from "react";
3+
import { useTheme } from "next-themes";
4+
5+
const BackgroundDots = ({ numDots = 50, speed = 0.5 }) => {
6+
const canvasRef = useRef(null);
7+
const dotsRef = useRef([]);
8+
const linesRef = useRef([]);
9+
const requestRef = useRef(null);
10+
11+
// 1. Hook de next-themes
12+
const { resolvedTheme } = useTheme();
13+
14+
// 2. Estado para evitar errores de hidratación
15+
const [mounted, setMounted] = useState(false);
16+
17+
useEffect(() => {
18+
setMounted(true);
19+
}, []);
20+
21+
const createDot = (width, height, existingDot = null) => {
22+
const dot = existingDot || {};
23+
dot.x = Math.random() * width;
24+
dot.y = Math.random() * height;
25+
dot.vx = (Math.random() - 0.5) * speed;
26+
dot.vy = (Math.random() - 0.5) * speed;
27+
dot.life = Math.random() * 300 + 100;
28+
dot.maxLife = dot.life;
29+
dot.opacity = 0;
30+
return dot;
31+
};
32+
33+
useEffect(() => {
34+
// Si no está montado aún, no hacemos nada (evita flash incorrecto)
35+
if (!mounted) return;
36+
37+
const canvas = canvasRef.current;
38+
if (!canvas) return;
39+
40+
const ctx = canvas.getContext("2d");
41+
let width = window.innerWidth;
42+
let height = window.innerHeight;
43+
44+
// 3. DEFINIR COLORES RGB SEGÚN EL TEMA
45+
// Si es dark, usamos Blanco (255,255,255). Si es light, usamos Gris Oscuro (20,20,20)
46+
const isDark = resolvedTheme === "dark";
47+
const r = isDark ? 255 : 20;
48+
const g = isDark ? 255 : 20;
49+
const b = isDark ? 255 : 20;
50+
51+
const handleResize = () => {
52+
width = window.innerWidth;
53+
height = window.innerHeight;
54+
canvas.width = width;
55+
canvas.height = height;
56+
dotsRef.current = Array.from({ length: numDots }, () =>
57+
createDot(width, height)
58+
);
59+
linesRef.current = [];
60+
};
61+
62+
window.addEventListener("resize", handleResize);
63+
handleResize();
64+
65+
const animate = () => {
66+
ctx.clearRect(0, 0, width, height);
67+
68+
// --- DIBUJAR PUNTOS ---
69+
dotsRef.current.forEach((dot) => {
70+
dot.x += dot.vx;
71+
dot.y += dot.vy;
72+
dot.life--;
73+
74+
if (dot.x < 0 || dot.x > width) dot.vx *= -1;
75+
if (dot.y < 0 || dot.y > height) dot.vy *= -1;
76+
77+
if (dot.life > dot.maxLife - 50)
78+
dot.opacity = Math.min(1, dot.opacity + 0.02);
79+
else if (dot.life < 50) dot.opacity = Math.max(0, dot.opacity - 0.02);
80+
81+
ctx.beginPath();
82+
ctx.arc(dot.x, dot.y, 3, 0, Math.PI * 2);
83+
// Usamos las variables r, g, b dinámicas
84+
ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${dot.opacity * 0.5})`;
85+
ctx.fill();
86+
87+
if (dot.life <= 0) createDot(width, height, dot);
88+
});
89+
90+
// --- DIBUJAR LÍNEAS ---
91+
if (Math.random() < 0.03 && dotsRef.current.length > 2) {
92+
const idx1 = Math.floor(Math.random() * dotsRef.current.length);
93+
let idx2 = Math.floor(Math.random() * dotsRef.current.length);
94+
while (idx1 === idx2) {
95+
idx2 = Math.floor(Math.random() * dotsRef.current.length);
96+
}
97+
linesRef.current.push({
98+
dot1: dotsRef.current[idx1],
99+
dot2: dotsRef.current[idx2],
100+
life: 80,
101+
maxLife: 90,
102+
});
103+
}
104+
105+
for (let i = linesRef.current.length - 1; i >= 0; i--) {
106+
const line = linesRef.current[i];
107+
const dx = line.dot1.x - line.dot2.x;
108+
const dy = line.dot1.y - line.dot2.y;
109+
const dist = Math.sqrt(dx * dx + dy * dy);
110+
111+
if (dist < 300) {
112+
const opacity = (line.life / line.maxLife) * 0.4;
113+
ctx.beginPath();
114+
ctx.moveTo(line.dot1.x, line.dot1.y);
115+
ctx.lineTo(line.dot2.x, line.dot2.y);
116+
// Usamos las mismas variables r, g, b para las líneas
117+
ctx.strokeStyle = `rgba(${r}, ${g}, ${b}, ${opacity})`;
118+
ctx.lineWidth = 1;
119+
ctx.stroke();
120+
}
121+
122+
line.life--;
123+
if (line.life <= 0) linesRef.current.splice(i, 1);
124+
}
125+
126+
requestRef.current = requestAnimationFrame(animate);
127+
};
128+
129+
requestRef.current = requestAnimationFrame(animate);
130+
131+
return () => {
132+
window.removeEventListener("resize", handleResize);
133+
cancelAnimationFrame(requestRef.current);
134+
};
135+
// 4. AGREGAMOS resolvedTheme A LAS DEPENDENCIAS
136+
// Esto hace que el canvas se reinicie con nuevos colores cuando cambias el tema
137+
}, [numDots, speed, resolvedTheme, mounted]);
138+
139+
// Si no está montado, devolvemos null para evitar parpadeos
140+
if (!mounted) return null;
141+
142+
return (
143+
<canvas
144+
ref={canvasRef}
145+
style={{
146+
position: "fixed",
147+
top: 0,
148+
left: 0,
149+
width: "100%",
150+
height: "100%",
151+
zIndex: 0,
152+
pointerEvents: "none",
153+
}}
154+
/>
155+
);
156+
};
157+
158+
export default BackgroundDots;

0 commit comments

Comments
 (0)