Guía de React
Tabla de Contenidos
- Instalación
- Uso Básico
- Efecto Confeti
- Efecto Fuegos Artificiales
- Efecto Nieve
- Enlaces Interactivos
- Cambio de Tema
- ParticlesProvider
- Optimización de Rendimiento
- Configuración Personalizada
Instalación
npm install @tsparticles/react tsparticlesO con Yarn:
yarn add @tsparticles/react tsparticles@tsparticles/react es el wrapper oficial de React. El paquete tsparticles es el motor central.
Uso Básico
La configuración más simple: renderiza el componente <Particles /> con un objeto de opciones.
import { useCallback } from "react";
import Particles from "@tsparticles/react";
export default function App() {
const particlesLoaded = useCallback(async (container) => {
console.log("Contenedor de partículas cargado", container);
}, []);
const options = {
fpsLimit: 120,
particles: {
number: { value: 80 },
color: { value: "#00d4ff" },
shape: { type: "circle" },
opacity: { value: 0.6 },
size: { value: { min: 2, max: 5 } },
move: {
enable: true,
speed: 2,
outModes: { default: "bounce" },
},
},
background: { color: "#0d1117" },
};
return <Particles id="tsparticles" particlesLoaded={particlesLoaded} options={options} />;
}Importante: El componente <Particles /> requiere que el motor se inicialice primero. Usa initParticlesEngine de @tsparticles/react o el <ParticlesProvider> para cargar tus presets antes de renderizar el componente.
Efecto Confeti
Renderiza una explosión de confeti usando el preset de confeti.
import Particles from "@tsparticles/react";
export default function Confetti() {
const options = {
preset: "confetti",
fullScreen: { enable: true, zIndex: -1 },
confetti: {
colors: ["#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff"],
particleCount: 150,
spread: 70,
origin: { x: 0.5, y: 0.5 },
},
};
return <Particles id="confetti" options={options} />;
}Asegúrate de haber cargado el preset de confeti:
npm install @tsparticles/preset-confettiLuego regístralo en el punto de entrada de tu aplicación:
import { initParticlesEngine } from "@tsparticles/react";
import { loadConfettiPreset } from "@tsparticles/preset-confetti";
initParticlesEngine(async (engine) => {
await loadConfettiPreset(engine);
});Efecto Fuegos Artificiales
Un despliegue de fuegos artificiales a pantalla completa.
import { useCallback, useMemo } from "react";
import Particles from "@tsparticles/react";
export default function Fireworks() {
const particlesLoaded = useCallback(async (container) => {
console.log("Fuegos artificiales cargados", container);
}, []);
const options = useMemo(
() => ({
preset: "fireworks",
fullScreen: { enable: true, zIndex: -1 },
fireworks: {
background: "#000000",
brightness: 100,
colors: ["#ff0000", "#00ff00", "#0000ff", "#ffff00", "#ff00ff", "#00ffff"],
intensity: 30,
life: { min: 4, max: 8 },
traces: 20,
explosion: { min: 30, max: 60 },
},
}),
[],
);
return <Particles id="fireworks" particlesLoaded={particlesLoaded} options={options} />;
}Instala el preset:
npm install @tsparticles/preset-fireworksEfecto Nieve
Suaves copos de nieve cayendo usando el preset de nieve.
import { useCallback, useMemo } from "react";
import Particles from "@tsparticles/react";
export default function Snow() {
const particlesLoaded = useCallback(async (container) => {
console.log("Nieve cargada", container);
}, []);
const options = useMemo(
() => ({
preset: "snow",
fullScreen: { enable: true, zIndex: -1 },
snow: {
color: "#ffffff",
opacity: { min: 0.3, max: 0.9 },
size: { min: 1, max: 4 },
speed: { min: 0.5, max: 2 },
wobble: true,
},
}),
[],
);
return <Particles id="snow" particlesLoaded={particlesLoaded} options={options} />;
}Instala el preset:
npm install @tsparticles/preset-snowEnlaces Interactivos
Una red de nodos conectados con agarre al pasar el ratón y empuje al hacer clic.
import { useCallback, useMemo } from "react";
import Particles from "@tsparticles/react";
export default function InteractiveLinks() {
const particlesLoaded = useCallback(async (container) => {
console.log("Enlaces interactivos cargados", container);
}, []);
const options = useMemo(
() => ({
fpsLimit: 60,
particles: {
number: { value: 80, density: { enable: true } },
color: { value: "#00d4ff" },
shape: { type: "circle" },
opacity: { value: 0.6 },
size: { value: { min: 1, max: 4 } },
links: {
enable: true,
distance: 150,
color: "#00d4ff",
opacity: 0.4,
width: 1,
},
move: {
enable: true,
speed: 1.5,
outModes: { default: "bounce" },
},
},
interactivity: {
events: {
onHover: { enable: true, mode: "grab" },
onClick: { enable: true, mode: "push" },
},
modes: {
grab: { distance: 180, links: { opacity: 0.8 } },
push: { quantity: 4 },
},
},
background: { color: "#0d1117" },
}),
[],
);
return <Particles id="interactive-links" particlesLoaded={particlesLoaded} options={options} />;
}Cambio de Tema
Define múltiples temas y cambia entre ellos con un clic de botón.
import { useCallback, useMemo, useRef, useState } from "react";
import Particles from "@tsparticles/react";
export default function ThemeSwitcher() {
const containerRef = useRef(null);
const [currentTheme, setCurrentTheme] = useState("dark");
const particlesLoaded = useCallback(async (container) => {
containerRef.current = container;
}, []);
const options = useMemo(
() => ({
fpsLimit: 60,
particles: {
number: { value: 60 },
color: { value: "#00d4ff" },
shape: { type: "circle" },
opacity: { value: 0.6 },
size: { value: { min: 2, max: 5 } },
links: { enable: true, distance: 150, color: "#00d4ff", opacity: 0.3 },
move: { enable: true, speed: 1.5, outModes: { default: "bounce" } },
},
background: { color: "#0d1117" },
themes: [
{
name: "dark",
default: { value: true },
options: {
background: { color: "#0d1117" },
particles: { color: { value: "#00d4ff" }, links: { color: "#00d4ff" } },
},
},
{
name: "light",
options: {
background: { color: "#f5f5f5" },
particles: { color: { value: "#e74c3c" }, links: { color: "#333333" } },
},
},
{
name: "forest",
options: {
background: { color: "#1a3a1a" },
particles: { color: { value: "#7ec850" }, links: { color: "#7ec850" } },
},
},
],
}),
[],
);
const switchTheme = useCallback((theme) => {
setCurrentTheme(theme);
if (containerRef.current) {
containerRef.current.loadTheme(theme);
}
}, []);
return (
<div style={{ position: "relative", width: "100%", height: "100vh" }}>
<Particles id="theme-switcher" particlesLoaded={particlesLoaded} options={options} />
<div
style={{
position: "absolute",
bottom: 20,
left: "50%",
transform: "translateX(-50%)",
zIndex: 10,
display: "flex",
gap: 8,
}}
>
<button onClick={() => switchTheme("dark")} style={btnStyle(currentTheme === "dark")}>
Oscuro
</button>
<button onClick={() => switchTheme("light")} style={btnStyle(currentTheme === "light")}>
Claro
</button>
<button onClick={() => switchTheme("forest")} style={btnStyle(currentTheme === "forest")}>
Bosque
</button>
</div>
</div>
);
}
const btnStyle = (active) => ({
padding: "8px 16px",
border: "none",
borderRadius: 6,
background: active ? "#333" : "#666",
color: "#fff",
cursor: "pointer",
});ParticlesProvider
Usa ParticlesProvider para inicializar el motor una vez en la raíz de la aplicación. Este es el enfoque recomendado cuando tienes múltiples componentes de partículas o usas presets personalizados.
// App.jsx
import { ParticlesProvider } from "@tsparticles/react";
import { loadSlim } from "@tsparticles/slim";
import Home from "./Home";
const engineInit = async (engine) => {
await loadSlim(engine);
// Carga presets adicionales aquí:
// await loadConfettiPreset(engine);
// await loadFireworksPreset(engine);
// await loadSnowPreset(engine);
};
export default function App() {
return (
<ParticlesProvider load={engineInit}>
<Home />
</ParticlesProvider>
);
}// Home.jsx
import Particles from "@tsparticles/react";
export default function Home() {
return (
<Particles
id="tsparticles"
options={{
particles: {
number: { value: 50 },
color: { value: "#ff6b6b" },
shape: { type: "circle" },
opacity: { value: 0.7 },
size: { value: { min: 2, max: 6 } },
move: { enable: true, speed: 1, outModes: { default: "bounce" } },
},
background: { color: "#1a1a2e" },
}}
/>
);
}Al envolver tu árbol con ParticlesProvider, cada componente hijo <Particles /> hereda la misma instancia del motor. Esto evita reinicializar el motor en cada montaje.
Optimización de Rendimiento
Siempre memoiza callbacks y opciones para evitar renderizaciones innecesarias.
import { useCallback, useMemo, useState } from "react";
import Particles from "@tsparticles/react";
export default function PerformanceExample() {
const [visible, setVisible] = useState(true);
// Memoiza el callback — referencia estable entre renderizaciones
const particlesLoaded = useCallback(async (container) => {
// Se llama una vez por montaje del contenedor
console.log("Contenedor listo", container?.id);
}, []);
// Memoiza el objeto de opciones — solo se recalcula cuando las dependencias cambian
const options = useMemo(
() => ({
fpsLimit: 60,
particles: {
number: { value: 100, density: { enable: true } },
color: { value: ["#ff6b6b", "#feca57", "#48dbfb"] },
shape: { type: "circle" },
opacity: { value: { min: 0.3, max: 0.7 } },
size: { value: { min: 2, max: 5 } },
links: {
enable: true,
distance: 120,
color: "random",
opacity: 0.3,
},
move: {
enable: true,
speed: 1,
outModes: { default: "bounce" },
},
},
background: { color: "#0d1117" },
}),
[],
);
// Reduce la resolución del canvas en dispositivos de baja potencia
const responsiveOptions = useMemo(
() => ({
...options,
detectRetina: window.devicePixelRatio <= 2,
fpsLimit: window.innerWidth < 768 ? 30 : 60,
}),
[options],
);
return (
<div>
<button onClick={() => setVisible((v) => !v)}>{visible ? "Ocultar" : "Mostrar"} Partículas</button>
{visible && <Particles id="perf-particles" particlesLoaded={particlesLoaded} options={responsiveOptions} />}
</div>
);
}Consejos clave:
- Usa siempre
useMemopara el objetooptions. - Usa siempre
useCallbackpara el manejadorparticlesLoaded. - Reduce
fpsLimiten dispositivos móviles. - Establece
detectRetina: falseen dispositivos con relación de píxeles >2 para reducir el tamaño del canvas a la mitad. - Desmonta condicionalmente
<Particles />cuando esté fuera de la pantalla.
Reactive Behavior
The <Particles> component reacts to prop changes at runtime:
id,options, orurlchange → the existing container is destroyed and particles are reloaded with the new values.themechange →loadThemeis called on the existing container. This requires the optional@tsparticles/plugin-themespackage to be loaded (otherwise it is a safe no-op).
On component unmount, the particles container is automatically destroyed — no orphan animations remain.
Configuración Personalizada
Un ejemplo personalizado completo que combina múltiples formas, interactividad, temas y un fondo degradado.
import { useCallback, useMemo } from "react";
import Particles from "@tsparticles/react";
export default function CustomConfig() {
const particlesLoaded = useCallback(async (container) => {
console.log("Configuración personalizada cargada", container);
}, []);
const options = useMemo(
() => ({
fullScreen: { enable: true, zIndex: 0 },
fpsLimit: 60,
particles: {
number: { value: 60, density: { enable: true, width: 800, height: 800 } },
color: {
value: ["#ff6b6b", "#feca57", "#48dbfb", "#ff9ff3", "#54a0ff"],
},
shape: {
type: ["circle", "triangle", "polygon"],
options: {
polygon: { sides: 6 },
},
},
opacity: { value: { min: 0.4, max: 0.8 } },
size: { value: { min: 3, max: 8 } },
links: {
enable: true,
distance: 200,
color: "#ffffff",
opacity: 0.15,
width: 1,
},
move: {
enable: true,
speed: 2,
direction: "none",
random: true,
outModes: { default: "out" },
},
},
interactivity: {
events: {
onHover: { enable: true, mode: "attract" },
onClick: { enable: true, mode: "repulse" },
},
modes: {
attract: { distance: 200, duration: 0.4, factor: 1 },
repulse: { distance: 200, duration: 0.4 },
},
},
background: { color: "#0f0f23" },
themes: [
{
name: "light",
default: { value: false },
options: {
background: { color: "#f0f0f5" },
particles: {
color: { value: ["#e74c3c", "#2ecc71", "#3498db", "#f1c40f"] },
links: { color: "#333333", opacity: 0.2 },
opacity: { value: { min: 0.5, max: 0.9 } },
},
},
},
],
}),
[],
);
return <Particles id="custom-config" particlesLoaded={particlesLoaded} options={options} />;
}Ahora has cubierto los patrones principales para usar tsParticles en React. Cada ejemplo es autónomo y está listo para colocar en un archivo de componente.
