UNPKG

v12-ui

Version:

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

223 lines (222 loc) 8.92 kB
import { jsx as L } from "react/jsx-runtime"; import { useRef as w, useEffect as E } from "react"; import { cn as y } from "../utils/utils.js"; class p { canvas; ctx; particles = []; mouse = null; animationId = null; dpr; config; imageElement = null; constructor(e, i) { this.canvas = e; const a = e.getContext("2d", { willReadFrequently: !0 }); if (!a) throw new Error("No se pudo obtener el contexto 2D del canvas"); this.ctx = a, this.dpr = window.devicePixelRatio || 1, this.config = i, 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((i, a) => { const t = new Image(), n = () => { this.imageElement = t, t.removeEventListener("load", n), t.removeEventListener("error", s), i(); }, s = (o) => { t.removeEventListener("load", n), t.removeEventListener("error", s), a(new Error(`Error al cargar la imagen desde URL: ${e} - ${o.type}`)); }; t.addEventListener("load", n), t.addEventListener("error", s), t.crossOrigin = "anonymous", t.src = e, setTimeout(() => { t.removeEventListener("load", n), t.removeEventListener("error", s), a(new Error(`Timeout: La imagen desde URL ${e} tardó demasiado en cargar`)); }, 1e4); }); } async loadImageElement(e) { return await new Promise((i, a) => { if (e.complete && e.naturalWidth > 0) this.imageElement = e, i(); else { const t = () => { this.imageElement = e, e.removeEventListener("load", t), e.removeEventListener("error", n), i(); }, n = (s) => { e.removeEventListener("load", t), e.removeEventListener("error", n), a(new Error(`Error al cargar la imagen: ${s.type}`)); }; e.addEventListener("load", t), e.addEventListener("error", n), setTimeout(() => { e.removeEventListener("load", t), e.removeEventListener("error", n), a(new Error("Timeout: La imagen tardó demasiado en cargar")); }, 1e4); } }); } async loadSVGAsImage(e) { return await new Promise((i, a) => { const t = new Blob([e], { type: "image/svg+xml" }), n = URL.createObjectURL(t), s = new Image(), o = () => { this.imageElement = s, URL.revokeObjectURL(n), s.removeEventListener("load", o), s.removeEventListener("error", r), i(); }, r = (l) => { URL.revokeObjectURL(n), s.removeEventListener("load", o), s.removeEventListener("error", r), a(new Error(`Error al cargar SVG como imagen: ${l.type}`)); }; s.addEventListener("load", o), s.addEventListener("error", r), s.crossOrigin = "anonymous", s.src = n, setTimeout(() => { URL.revokeObjectURL(n), s.removeEventListener("load", o), s.removeEventListener("error", r), a(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 i = this.canvas.width / this.dpr, a = this.canvas.height / this.dpr, n = Math.min(i / e.naturalWidth, a / e.naturalHeight) * 0.8, s = e.naturalWidth * n, o = e.naturalHeight * n, r = (i - s) / 2, l = (a - o) / 2; this.ctx.drawImage(e, r, l, s, o); } extractPositions(e) { const i = [], { data: a, width: t, height: n } = e, o = Math.max(1, 4); let r = 0; for (let c = 0; c < a.length; c += 4) { const [h, g, d, v] = a.slice(c, c + 4); v > 50 && (h < 220 || g < 220 || d < 220) && r++; } const l = Math.max( 1, Math.floor(Math.sqrt(r / (this.config.particles || 6e3))) ), m = Math.min(o, l); for (let c = 0; c < n; c += m) for (let h = 0; h < t; h += m) { const g = (c * t + h) * 4, [d, v, u, x] = a.slice(g, g + 4); x > 50 && (d < 220 || v < 220 || u < 220) && i.push({ x: h / this.dpr, y: c / this.dpr, color: `rgb(${d},${v},${u})` }); } return i; } generateParticles() { if (!this.imageElement) { console.warn("No hay imagen cargada para generar partículas"); return; } const e = this.createImageMask(), i = this.extractPositions(e); if (i.length === 0) { console.warn("No se encontraron píxeles visibles en la imagen"); return; } const a = Math.max(this.canvas.width, this.canvas.height) * 2; this.particles = i.map((t, n) => ({ 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) * a, y: t.y + (Math.random() - 0.5) * a, vx: 0, vy: 0, color: t.color, phase: Math.random() * Math.PI * 2, // 0…2π age: 0, order: n })); } updateParticle(e) { const i = e.tx - e.x, a = e.ty - e.y; if (this.mouse) { const s = this.mouse.x - e.x, o = this.mouse.y - e.y, r = Math.hypot(s, o), m = e.order * 0.08, h = (r - m) * 0.1; r > 0 && (e.vx += s / r * h, e.vy += o / r * h); } const t = 0.04, n = 0.73; e.vx += i * t, e.vy += a * t, e.vx *= n, e.vy *= n, e.x += e.vx, e.y += e.vy; } drawParticles() { const e = this.canvas.width / this.dpr, i = this.canvas.height / this.dpr; this.ctx.clearRect(0, 0, e, i); for (const a of this.particles) { this.updateParticle(a), a.age += 1; const t = (Math.sin(a.phase + a.age * 0.15) + 1) / 2; this.ctx.fillStyle = this.config.color || a.color || "#fff", this.config.glow && (this.ctx.globalAlpha = t), this.ctx.beginPath(), this.ctx.arc(a.x, a.y, this.config.dotSize, 0, Math.PI * 2), this.ctx.fill(); } this.ctx.globalAlpha = 1; } handleMouseMove = (e) => { const i = this.canvas.getBoundingClientRect(); this.mouse = { x: (e.clientX - i.left) * (this.canvas.width / this.dpr / i.width), y: (e.clientY - i.top) * (this.canvas.height / this.dpr / i.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: i, particles: a = 750, dotSize: t = 0.9, repulsion: n = 80, friction: s = 0.82, returnSpeed: o = 0.01, color: r, glow: l = !0, className: m, ...c }) { const h = w(null); return E(() => { const g = h.current; if (!g) return; if (e && (!e.complete || e.naturalWidth === 0)) { console.warn("MagicLogo: imageElement no está completamente cargado"); return; } let d; if (f != null) d = { imageUrl: f }; else if (e != null) d = { imageElement: e }; else if (i != null) d = { svgContent: i }; else { console.warn("MagicLogo: falta una fuente de imagen"); return; } const v = { particles: a, dotSize: t, repulsion: n, friction: s, returnSpeed: o, color: r, glow: l, ...d }, u = new p(g, v); return () => { u && u.destroy(); }; }, [f, e, i, a, t, n, s, o, r, l]), /* @__PURE__ */ L( "canvas", { ref: h, ...c, className: y("block mx-auto overflow-visible z-0 w-auto h-auto", m) } ); } export { R as MagicMouseFollower };