UNPKG

v12-ui

Version:

A React component library with a focus on utility-first design and accessibility.

172 lines (171 loc) 6.16 kB
import { jsx as p } from "react/jsx-runtime"; import { useRef as w, useEffect as P } from "react"; import { useDataTheme as b } from "../Hooks/useDataTheme.js"; import { cn as I } from "../utils/utils.js"; class C { canvas; ctx; particles = []; mouse = null; animationId = null; dpr; config; constructor(t, s) { this.canvas = t; const i = t.getContext("2d"); if (!i) throw new Error("No se pudo obtener el contexto 2D del canvas"); this.ctx = i, this.dpr = window.devicePixelRatio || 1, this.config = s, this.setupCanvas(), this.generateParticles(), this.animate = this.animate.bind(this), this.animate(); } setupCanvas() { const t = this.canvas.getBoundingClientRect(); this.canvas.width = t.width * this.dpr, this.canvas.height = t.height * this.dpr, this.canvas.style.width = `${t.width}px`, this.canvas.style.height = `${t.height}px`, this.ctx.scale(this.dpr, this.dpr), this.ctx.imageSmoothingEnabled = !0, this.ctx.imageSmoothingQuality && (this.ctx.imageSmoothingQuality = "high"); } createTextMask() { return this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height), this.ctx.font = `bold ${this.config.fontSize || 50}px ${this.config.fontFamily || "sans-serif"}`, this.ctx.textAlign = "center", this.ctx.textBaseline = "middle", this.ctx.fillStyle = `${this.config.color || "#fff"}`, this.ctx.fillText(this.config.text, this.canvas.width / 2, this.canvas.height / 2), this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); } extractPositions(t) { const s = [], { data: i, width: a, height: f } = t, o = Math.max(1, 4); let e = 0; for (let n = 0; n < i.length; n += 4) { const [l] = i.slice(n, n + 4); l > 50 && e++; } const c = Math.max( 1, Math.floor(Math.sqrt(e / (this.config.particles || 6e3))) ), h = Math.min(o, c); for (let n = 0; n < f; n += h) for (let l = 0; l < a; l += h) { const d = (n * a + l) * 4, [x] = i.slice(d, d + 4); x > 50 && s.push({ x: l / this.dpr, y: n / this.dpr }); } return s; } generateParticles() { const t = this.createTextMask(), s = this.extractPositions(t); if (s.length === 0) { console.warn("No se encontraron píxeles visibles en la imagen"); return; } const i = Math.max(this.canvas.width, this.canvas.height) * 2; this.particles = s.map((a) => ({ tx: a.x, // posición final (píxel real) ty: a.y, // posición inicial: fuera del canvas x: a.x + (Math.random() - 0.5) * i, y: a.y + (Math.random() - 0.5) * i, vx: 0, vy: 0, phase: Math.random() * Math.PI * 2, // 0…2π age: 0, isImmune: Math.random() < 0.05 // ≈ 5 % no se repelen })); } updateParticle(t) { const s = t.tx - t.x, i = t.ty - t.y; if (this.mouse && this.config.attractMode) { const r = this.mouse.x - t.x, o = this.mouse.y - t.y, e = Math.hypot(r, o); if (e < this.config.repulsion && e > 0) { const h = 10 * Math.max(0, 1 - e / this.config.repulsion); t.vx += r / e * h, t.vy += o / e * h; } } else if (this.mouse && !this.config.attractMode) { const r = this.mouse.x - t.x, o = this.mouse.y - t.y, e = Math.hypot(r, o); if (e < this.config.repulsion && e > 0) { let c; this.config.trace ? t.isImmune ? c = (this.config.repulsion - e) / this.config.repulsion * 0.1 : c = Math.max(0, 1 - e / this.config.repulsion) : c = Math.max(0, 1 - e / this.config.repulsion); const h = 20 * c; t.vx -= r / e * h, t.vy -= o / e * h; } } const a = 0.04, f = 0.73; t.vx += s * a, t.vy += i * a, t.vx *= f, t.vy *= f, t.x += t.vx, t.y += t.vy; } drawParticles() { const t = this.canvas.width / this.dpr, s = this.canvas.height / this.dpr; this.ctx.clearRect(0, 0, t, s); for (const i of this.particles) { this.updateParticle(i), i.age += 1; const a = (Math.sin(i.phase + i.age * 0.15) + 1) / 2; this.ctx.fillStyle = this.config.color || "#fff", this.config.glow && (this.ctx.globalAlpha = a), this.ctx.beginPath(), this.ctx.arc(i.x, i.y, this.config.dotSize, 0, Math.PI * 2), this.ctx.fill(); } this.ctx.globalAlpha = 1; } handleMouseMove = (t) => { const s = this.canvas.getBoundingClientRect(); this.mouse = { x: (t.clientX - s.left) * (this.canvas.width / s.width), y: (t.clientY - s.top) * (this.canvas.height / s.height) }; }; handleMouseLeave = () => { this.mouse = null; }; animate() { this.drawParticles(), this.canvas.addEventListener("mousemove", this.handleMouseMove), this.canvas.addEventListener("mouseleave", this.handleMouseLeave), this.animationId = requestAnimationFrame(this.animate); } destroy() { this.canvas.removeEventListener("mousemove", this.handleMouseMove), this.canvas.removeEventListener("mouseleave", this.handleMouseLeave), this.animationId && cancelAnimationFrame(this.animationId); } } function A({ text: m = "Magic Text", particles: t = 500, dotSize: s = 0.9, repulsion: i = 50, friction: a = 0.82, returnSpeed: f = 0.01, fontFamily: r = "sans-serif", fontSize: o = 50, color: e, glow: c = !0, trace: h = !0, attractMode: n = !1, className: l, ...d }) { const { theme: x } = b(), g = e || (x === "dark" ? "#fff" : "#000"); console.log("Theme:", x, "Color detected:", g); const v = w(null); return P(() => { const u = v.current; if (!u) return; if (!m) { console.warn("MagicText: text needed"); return; } const M = { text: m, particles: t, dotSize: s, repulsion: i, friction: a, returnSpeed: f, fontFamily: r, fontSize: o, color: g, glow: c, trace: h, attractMode: n }, y = new C(u, M); return () => { y && y.destroy(); }; }, [m, t, s, i, a, f, r, o, x, e, c, h, n]), /* @__PURE__ */ p( "canvas", { ref: v, ...d, className: I("block mx-auto overflow-visible z-0 w-fit h-fit", l) } ); } export { A as MagicText };