UNPKG

v12-ui

Version:

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

239 lines (238 loc) 9.63 kB
import { jsx as w } from "react/jsx-runtime"; import { useRef as E, useEffect as y } from "react"; import { cn as p } from "../utils/utils.js"; class M { canvas; ctx; particles = []; mouse = null; animationId = null; dpr; config; imageElement = null; constructor(e, s) { this.canvas = e; const i = e.getContext("2d", { willReadFrequently: !0 }); 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.initializeEffect(); } async initializeEffect() { try { this.config.imageUrl ? (console.log("MagicLogo: Cargando imagen desde URL:", this.config.imageUrl), await this.loadImage(this.config.imageUrl)) : this.config.svgContent ? (console.log("MagicLogo: Cargando SVG como imagen"), await this.loadSVGAsImage(this.config.svgContent)) : this.config.imageElement && (console.log("MagicLogo: Cargando elemento de imagen existente"), await this.loadImageElement(this.config.imageElement)), console.log("MagicLogo: Imagen cargada exitosamente, generando partículas..."), this.generateParticles(), this.setupEventListeners(), this.animate = this.animate.bind(this), this.animate(); } catch (e) { console.error("MagicLogo: Error al inicializar el efecto:", e); } } async loadImage(e) { return await new Promise((s, i) => { const t = new Image(), o = () => { this.imageElement = t, t.removeEventListener("load", o), t.removeEventListener("error", a), s(); }, a = (r) => { t.removeEventListener("load", o), t.removeEventListener("error", a), i(new Error(`Error al cargar la imagen desde URL: ${e} - ${r.type}`)); }; t.addEventListener("load", o), t.addEventListener("error", a), t.crossOrigin = "anonymous", t.src = e, setTimeout(() => { t.removeEventListener("load", o), t.removeEventListener("error", a), i(new Error(`Timeout: La imagen desde URL ${e} tardó demasiado en cargar`)); }, 1e4); }); } async loadImageElement(e) { return await new Promise((s, i) => { if (e.complete && e.naturalWidth > 0) this.imageElement = e, s(); else { const t = () => { this.imageElement = e, e.removeEventListener("load", t), e.removeEventListener("error", o), s(); }, o = (a) => { e.removeEventListener("load", t), e.removeEventListener("error", o), i(new Error(`Error al cargar la imagen: ${a.type}`)); }; e.addEventListener("load", t), e.addEventListener("error", o), setTimeout(() => { e.removeEventListener("load", t), e.removeEventListener("error", o), i(new Error("Timeout: La imagen tardó demasiado en cargar")); }, 1e4); } }); } async loadSVGAsImage(e) { return await new Promise((s, i) => { const t = new Blob([e], { type: "image/svg+xml" }), o = URL.createObjectURL(t), a = new Image(), r = () => { this.imageElement = a, URL.revokeObjectURL(o), a.removeEventListener("load", r), a.removeEventListener("error", n), s(); }, n = (c) => { URL.revokeObjectURL(o), a.removeEventListener("load", r), a.removeEventListener("error", n), i(new Error(`Error al cargar SVG como imagen: ${c.type}`)); }; a.addEventListener("load", r), a.addEventListener("error", n), a.crossOrigin = "anonymous", a.src = o, setTimeout(() => { URL.revokeObjectURL(o), a.removeEventListener("load", r), a.removeEventListener("error", n), i(new Error("Timeout: El SVG tardó demasiado en cargar")); }, 1e4); }); } setupCanvas() { const e = this.canvas.getBoundingClientRect(); this.canvas.width = e.width * this.dpr, this.canvas.height = e.height * this.dpr, this.canvas.style.width = `${e.width}px`, this.canvas.style.height = `${e.height}px`, this.ctx.scale(this.dpr, this.dpr), this.ctx.imageSmoothingEnabled = !0, this.ctx.imageSmoothingQuality && (this.ctx.imageSmoothingQuality = "high"); } createImageMask() { return this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height), this.imageElement && this.drawImageToCanvas(this.imageElement), this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height); } drawImageToCanvas(e) { const s = this.canvas.width / this.dpr, i = this.canvas.height / this.dpr, o = Math.min(s / e.naturalWidth, i / e.naturalHeight) * 0.8, a = e.naturalWidth * o, r = e.naturalHeight * o, n = (s - a) / 2, c = (i - r) / 2; this.ctx.drawImage(e, n, c, a, r); } extractPositions(e) { const s = [], { data: i, width: t, height: o } = e, r = Math.max(1, 4); let n = 0; for (let h = 0; h < i.length; h += 4) { const [d, u, g, m] = i.slice(h, h + 4); m > 50 && (d < 220 || u < 220 || g < 220) && n++; } const c = Math.max( 1, Math.floor(Math.sqrt(n / (this.config.particles || 6e3))) ), l = Math.min(r, c); for (let h = 0; h < o; h += l) for (let d = 0; d < t; d += l) { const u = (h * t + d) * 4, [g, m, v, x] = i.slice(u, u + 4); x > 50 && (g < 220 || m < 220 || v < 220) && s.push({ x: d / this.dpr, y: h / this.dpr, color: `rgb(${g},${m},${v})` }); } return s; } generateParticles() { if (!this.imageElement) { console.warn("No hay imagen cargada para generar partículas"); return; } const e = this.createImageMask(), s = this.extractPositions(e); 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((t) => ({ tx: t.x, // posición final (píxel real) ty: t.y, // posición inicial: fuera del canvas x: t.x + (Math.random() - 0.5) * i, y: t.y + (Math.random() - 0.5) * i, vx: 0, vy: 0, color: t.color, phase: Math.random() * Math.PI * 2, // 0…2π age: 0, isImmune: Math.random() < 0.05 // ≈ 5 % no se repelen })); } updateParticle(e) { const s = e.tx - e.x, i = e.ty - e.y; if (this.mouse && this.config.attractMode) { const a = this.mouse.x - e.x, r = this.mouse.y - e.y, n = Math.hypot(a, r); if (n < this.config.repulsion && n > 0) { const l = 10 * Math.max(0, 1 - n / this.config.repulsion); e.vx += a / n * l, e.vy += r / n * l; } } else if (this.mouse && !this.config.attractMode) { const a = this.mouse.x - e.x, r = this.mouse.y - e.y, n = Math.hypot(a, r); if (n < this.config.repulsion && n > 0) { let c; this.config.trace ? e.isImmune ? c = (this.config.repulsion - n) / this.config.repulsion * 0.1 : c = Math.max(0, 1 - n / this.config.repulsion) : c = Math.max(0, 1 - n / this.config.repulsion); const l = 20 * c; e.vx -= a / n * l, e.vy -= r / n * l; } } const t = 0.04, o = 0.73; e.vx += s * t, e.vy += i * t, e.vx *= o, e.vy *= o, e.x += e.vx, e.y += e.vy; } drawParticles() { const e = this.canvas.width / this.dpr, s = this.canvas.height / this.dpr; this.ctx.clearRect(0, 0, e, s); for (const i of this.particles) { this.updateParticle(i), i.age += 1; const t = (Math.sin(i.phase + i.age * 0.15) + 1) / 2; this.ctx.fillStyle = this.config.color || i.color || "#fff", this.config.glow && (this.ctx.globalAlpha = t), 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 = (e) => { const s = this.canvas.getBoundingClientRect(); this.mouse = { x: (e.clientX - s.left) * (this.canvas.width / this.dpr / s.width), y: (e.clientY - s.top) * (this.canvas.height / this.dpr / s.height) }; }; handleMouseLeave = () => { this.mouse = null; }; animate() { this.drawParticles(), this.animationId = requestAnimationFrame(this.animate); } setupEventListeners() { this.canvas.addEventListener("mousemove", this.handleMouseMove), this.canvas.addEventListener("mouseleave", this.handleMouseLeave); } destroy() { this.canvas.removeEventListener("mousemove", this.handleMouseMove), this.canvas.removeEventListener("mouseleave", this.handleMouseLeave), this.animationId && cancelAnimationFrame(this.animationId); } } function R({ imageUrl: f, imageElement: e, svgContent: s, particles: i = 750, dotSize: t = 0.9, repulsion: o = 80, friction: a = 0.82, returnSpeed: r = 0.01, color: n, glow: c = !0, trace: l = !0, attractMode: h = !1, className: d, ...u }) { const g = E(null); return y(() => { const m = g.current; if (!m) return; if (e && (!e.complete || e.naturalWidth === 0)) { console.warn("MagicLogo: imageElement no está completamente cargado"); return; } let v; if (f != null) v = { imageUrl: f }; else if (e != null) v = { imageElement: e }; else if (s != null) v = { svgContent: s }; else { console.warn("MagicLogo: falta una fuente de imagen"); return; } const x = { particles: i, dotSize: t, repulsion: o, friction: a, returnSpeed: r, color: n, glow: c, trace: l, attractMode: h, ...v }, L = new M(m, x); return () => { L && L.destroy(); }; }, [f, e, s, i, t, o, a, r, n, c, l, h]), /* @__PURE__ */ w( "canvas", { ref: g, ...u, className: p("block mx-auto overflow-visible z-0 w-auto h-auto", d) } ); } export { R as MagicLogo };