UNPKG

vanta-react

Version:

React components for Vanta.js animated backgrounds with TypeScript support - CDN optimized with automatic library loading

696 lines (695 loc) 21.5 kB
import i, { useRef as te, useState as C, useMemo as ne, useCallback as oe, useEffect as U, Component as we } from "react"; let N = !1, _ = !1, me = !1, b = null, v = null; const W = { THREE: "https://cdn.jsdelivr.net/npm/three@0.134.0/build/three.min.js", P5: "https://cdn.jsdelivr.net/npm/p5@1.1.9/lib/p5.min.js", VANTA_BASE: "https://cdn.jsdelivr.net/npm/vanta@latest/dist/" }, ie = () => new Promise((e, r) => { if (N && window.THREE) { e(window.THREE); return; } if (b) { b.addEventListener("load", () => { e(window.THREE); }), b.addEventListener("error", r); return; } b = document.createElement("script"), b.src = W.THREE, b.onload = () => { N = !0, window.THREE ? e(window.THREE) : r(new Error("Three.js failed to load properly from CDN")); }, b.onerror = () => { r(new Error("Failed to load Three.js from CDN")); }, document.head.appendChild(b); }), se = () => new Promise((e, r) => { if (_ && window.p5) { e(window.p5); return; } if (v) { v.addEventListener("load", () => { e(window.p5); }), v.addEventListener("error", r); return; } v = document.createElement("script"), v.src = W.P5, v.onload = () => { _ = !0, window.p5 ? e(window.p5) : r(new Error("p5.js failed to load properly from CDN")); }, v.onerror = () => { r(new Error("Failed to load p5.js from CDN")); }, document.head.appendChild(v); }), B = (e) => new Promise((r, t) => { const n = `${W.VANTA_BASE}vanta.${e}.min.js`, a = window.VANTA; if (a != null && a[e.toUpperCase()]) { r(a[e.toUpperCase()]); return; } const c = document.createElement("script"); c.src = n, c.onload = () => { const f = window.VANTA, d = f == null ? void 0 : f[e.toUpperCase()]; d ? r(d) : t(new Error(`Failed to load Vanta effect: ${e}`)); }, c.onerror = () => { t(new Error(`Failed to load Vanta effect from CDN: ${e}`)); }, document.head.appendChild(c); }), Re = async (e = !1) => { const r = [ie()]; e && r.push(se()); const t = await Promise.all(r), n = t[0], a = e ? t[1] : void 0; return { THREE: n, ...e && { p5: a } }; }, Ae = () => ({ threeLoaded: N, p5Loaded: _, vantaLoaded: me, threeAvailable: !!window.THREE, p5Available: !!window.p5, vantaAvailable: !!window.VANTA }), ye = [ "birds", "cells", "clouds", "clouds2", "dots", "fog", "globe", "halo", "net", "rings", "ripple", "topology", "trunk", "waves" ], Ve = async () => { const e = ye.map( (r) => B(r).catch(() => null) ); await Promise.all(e); }, z = {}, V = {}, le = async (e) => { const r = e.toLowerCase(); if (z[r]) return z[r]; if (r in V) return V[r]; const t = (async () => { try { const n = await B(r); if (n && typeof n == "function") return z[r] = n, n; throw new Error(`Invalid effect creator received for "${r}"`); } catch { return null; } finally { delete V[r]; } })(); return V[r] = t, t; }, Pe = () => [ "birds", "cells", "clouds", "clouds2", "dots", "fog", "globe", "halo", "net", "rings", "ripple", "topology", "trunk", "waves" ], ze = () => Object.keys(z), He = () => { Object.keys(z).forEach((e) => { delete z[e]; }), Object.keys(V).forEach((e) => { delete V[e]; }); }; let H = !1, P = null, m = null; const ae = async () => H ? Promise.resolve() : P || (m && (m = null), P = (async () => { try { const e = [ ie().catch((n) => { throw new Error(`Failed to load THREE.js: ${n.message}`); }), se().catch((n) => { throw new Error(`Failed to load p5.js: ${n.message}`); }) ], [r, t] = await Promise.all(e); if (r && typeof window < "u") window.THREE = r; else throw new Error("Failed to load THREE.js properly from CDN"); if (t && typeof window < "u") window.p5 = t; else throw new Error("Failed to load p5.js properly from CDN"); if (typeof window < "u" && window.THREE && window.p5) H = !0; else throw new Error("Library assignment verification failed"); } catch (e) { throw m = e instanceof Error ? e : new Error("Unknown CDN preload error"), m; } })(), P), S = () => typeof window > "u" ? !1 : H && !!window.THREE && !!window.p5, he = () => { if (typeof window > "u" || !window.THREE) throw new Error("THREE.js is not preloaded from CDN. Call preloadLibraries() first."); return window.THREE; }, Ee = () => { if (typeof window > "u" || !window.p5) throw new Error("p5.js is not preloaded from CDN. Call preloadLibraries() first."); return window.p5; }, De = () => ({ isPreloaded: H, isLoading: !!P && !H, hasError: !!m, error: (m == null ? void 0 : m.message) || null, threeAvailable: !!(typeof window < "u" && window.THREE), p5Available: !!(typeof window < "u" && window.p5), loadSource: "CDN" }), ke = () => { H = !1, P = null, m = null, typeof window < "u" && (window.THREE = void 0, window.p5 = void 0); }, ge = () => /* @__PURE__ */ i.createElement("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", height: "100%", padding: "20px", backgroundColor: "var(--vanta-loading-bg, rgba(0, 0, 0, 0.1))", color: "var(--vanta-loading-text, #666)", fontFamily: "system-ui, -apple-system, sans-serif" } }, /* @__PURE__ */ i.createElement("div", { style: { width: "40px", height: "40px", border: "3px solid var(--vanta-loading-border, #e0e0e0)", borderTop: "3px solid var(--vanta-loading-accent, #007bff)", borderRadius: "50%", animation: "vanta-spin 1s linear infinite", marginBottom: "16px" } }), /* @__PURE__ */ i.createElement("style", null, ` @keyframes vanta-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } `), /* @__PURE__ */ i.createElement("p", { style: { margin: 0, fontSize: "14px", textAlign: "center" } }, "🌐 Vanta 라이브러리 로딩 중...")), be = ({ error: e, onRetry: r }) => /* @__PURE__ */ i.createElement("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", height: "100%", padding: "20px", backgroundColor: "var(--vanta-error-bg, rgba(255, 0, 0, 0.05))", color: "var(--vanta-error-text, #d32f2f)", fontFamily: "system-ui, -apple-system, sans-serif", textAlign: "center" } }, /* @__PURE__ */ i.createElement("div", { style: { fontSize: "48px", marginBottom: "16px" } }, "⚠️"), /* @__PURE__ */ i.createElement("h3", { style: { margin: "0 0 12px 0", fontSize: "16px", fontWeight: "600" } }, "Vanta 라이브러리 로딩 실패"), /* @__PURE__ */ i.createElement("p", { style: { margin: "0 0 20px 0", fontSize: "14px", opacity: 0.8, maxWidth: "300px" } }, e), /* @__PURE__ */ i.createElement( "button", { onClick: r, style: { padding: "8px 16px", backgroundColor: "var(--vanta-error-button-bg, #d32f2f)", color: "var(--vanta-error-button-text, white)", border: "none", borderRadius: "4px", fontSize: "14px", fontWeight: "500", cursor: "pointer", transition: "background-color 0.2s ease" }, onMouseOver: (t) => { t.currentTarget.style.backgroundColor = "var(--vanta-error-button-hover, #b71c1c)"; }, onMouseOut: (t) => { t.currentTarget.style.backgroundColor = "var(--vanta-error-button-bg, #d32f2f)"; } }, "🔄 다시 시도" )), Fe = ({ effect: e, options: r, className: t = "", style: n, background: a = !1, // 자동 로딩 관련 props autoLoad: c = !0, loadingComponent: f, errorComponent: d, retryCount: j = 3, retryDelay: T = 1e3, onLoadStart: M, onLoadSuccess: y, onLoadError: h }) => { const s = te(null), u = te(null), [de, O] = C(!1), [G, R] = C(null), [D, p] = C("idle"), [J, k] = C(null), [q, K] = C(0), Q = ne(() => r, [r]), L = ne(() => ["trunk", "topology", "dots"].includes(e), [e]), X = oe(() => () => { if (s.current && u.current) { const o = u.current; o.style.width = "100vw", o.style.height = "100vh", s.current.resize && s.current.resize(); } }, []), Y = oe(async () => { if (!(q >= j)) { K((o) => o + 1), k(null), p("loading"); try { await new Promise((o) => setTimeout(o, T)), await ae(), p("success"), y == null || y(); } catch (o) { const E = o instanceof Error ? o.message : "Unknown library loading error"; k(E), p("error"), h == null || h(E); } } }, [q, j, T, y, h]); U(() => { if (!c) { S() ? p("success") : (p("error"), k("Libraries not preloaded. Please call preloadLibraries() manually or set autoLoad=true.")); return; } if (S()) { p("success"); return; } let o = !0; return (async () => { if (o) { p("loading"), k(null), K(0), M == null || M(); try { await ae(), o && (p("success"), y == null || y()); } catch ($) { if (o) { const l = $ instanceof Error ? $.message : "Unknown library loading error"; k(l), p("error"), h == null || h(l); } } } })(), () => { o = !1; }; }, [c, M, y, h]), U(() => { if (D !== "success") return; let o = !0, E = null; return E || (E = (async () => { if (!(!u.current || !e)) { if (s.current) { try { s.current.destroy(); } catch (l) { console.warn("[Vanta] Error destroying previous effect:", l); } s.current = null; } o && (O(!0), R(null)); try { let g = 0; for (; g < 5 && o && !S(); ) g === 0 && console.warn("[Vanta] Libraries are not preloaded. Ensure preloadLibraries() is called before using Vanta components."), g++, await new Promise((A) => setTimeout(A, 100 * g)); if (!S() && o) { R("Libraries not ready after multiple attempts. Please ensure preloadLibraries() is called."); return; } let Z, ee; try { Z = he(), L && (ee = Ee()); } catch (A) { o && R(`Library access error: ${A instanceof Error ? A.message : "Unknown error"}`); return; } const I = await le(e); if (!I && o) { R(`Effect "${e}" not found`); return; } if (o && u.current && I) { if (a && u.current) { const w = u.current; w.style.position = "fixed", w.style.top = "0", w.style.left = "0", w.style.width = "100vw", w.style.height = "100vh", w.style.zIndex = "-10", w.offsetHeight; } const A = { el: u.current, THREE: Z, ...L && { p5: ee }, mouseControls: !0, touchControls: !0, gyroControls: !1, minHeight: a ? window.innerHeight : 200, minWidth: a ? window.innerWidth : 200, scale: 1, scaleMobile: 1, ...Q }; if (s.current = I(A), !s.current && o) { R(`Failed to create effect "${e}"`); return; } if (a && s.current && o) { const w = setTimeout(() => { if (s.current && u.current && o) try { s.current.resize && s.current.resize(); } catch (pe) { console.warn("[Vanta] Resize error:", pe); } }, 100), re = X(); window.addEventListener("resize", re); const ue = () => { window.removeEventListener("resize", re), clearTimeout(w); }; s.current._customCleanup = ue; } } } catch (l) { console.error(`Vanta.js effect "${e}" failed to initialize:`, l), o && R(`Failed to initialize effect "${e}"`); } finally { o && O(!1); } } })()), () => { if (o = !1, E) E.finally(() => { if (s.current) { const l = s.current; if (l._customCleanup) try { l._customCleanup(); } catch (g) { console.warn("[Vanta] Cleanup error:", g); } try { s.current.destroy(); } catch (g) { console.warn("[Vanta] Destroy error:", g); } s.current = null; } }); else if (s.current) { try { const l = s.current; l._customCleanup && l._customCleanup(), s.current.destroy(); } catch (l) { console.warn("[Vanta] Cleanup error:", l); } s.current = null; } }; }, [e, Q, a, X, L, D]); const fe = a ? "fixed inset-0 w-screen h-screen -z-10" : "w-full h-full"; return G && console.warn(`Vanta effect load error: ${G}`), D === "loading" ? f || /* @__PURE__ */ i.createElement(ge, null) : D === "error" ? typeof d == "function" ? d(J || "Unknown error", Y) : d || /* @__PURE__ */ i.createElement(be, { error: J || "Unknown error", onRetry: Y }) : D !== "success" ? null : i.createElement("div", { ref: u, className: `${fe} ${t}`, style: { // 로딩 중일 때는 투명도를 낮춰서 시각적 피드백 제공 (선택사항) opacity: de ? 0.7 : 1, transition: "opacity 0.3s ease-in-out", // 배경 모드일 때 추가 스타일 ...a && { top: 0, left: 0, width: "100vw", height: "100vh", overflow: "hidden" }, // 사용자 정의 스타일 추가 ...n } }); }; class je extends we { constructor(r) { super(r), this.retryTimeoutId = null, this.handleRetry = () => { this.setState({ hasError: !1, error: null, errorInfo: null }); }, this.handleRetryWithDelay = () => { this.retryTimeoutId = window.setTimeout(() => { this.handleRetry(); }, 1e3); }, this.state = { hasError: !1, error: null, errorInfo: null }; } static getDerivedStateFromError(r) { return { hasError: !0, error: r }; } componentDidCatch(r, t) { this.setState({ error: r, errorInfo: t }), console.error("Vanta Error Boundary caught an error:", r), console.error("Error Info:", t), this.props.onError && this.props.onError(r, t), (r.message.includes("ReactDebugCurrentFrame") || r.message.includes("React")) && console.warn("React lifecycle error detected. This might be related to library loading timing."); } componentWillUnmount() { this.retryTimeoutId && window.clearTimeout(this.retryTimeoutId); } render() { return this.state.hasError ? this.props.fallback ? this.props.fallback : i.createElement( "div", { style: { display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", height: "200px", padding: "20px", backgroundColor: "#f8f9fa", border: "1px solid #e9ecef", borderRadius: "8px", textAlign: "center", fontFamily: "Arial, sans-serif", color: "#495057" } }, i.createElement("div", { style: { fontSize: "48px", marginBottom: "16px", color: "#dc3545" } }, "⚠️"), i.createElement("h3", { style: { margin: "0 0 12px 0", fontSize: "18px", color: "#dc3545" } }, "Vanta Effect Error"), i.createElement("p", { style: { margin: "0 0 16px 0", fontSize: "14px", color: "#6c757d", maxWidth: "400px", lineHeight: "1.4" } }, "Something went wrong while loading the Vanta effect. This might be due to library loading issues."), // 개발 모드에서만 에러 세부사항 표시 (개발 환경 체크) typeof window < "u" && window.location.hostname === "localhost" && this.state.error && i.createElement( "details", { style: { marginBottom: "16px", maxWidth: "500px" } }, i.createElement("summary", { style: { fontSize: "12px", color: "#6c757d", cursor: "pointer", marginBottom: "8px" } }, "Error Details (Development Only)"), i.createElement("pre", { style: { fontSize: "10px", color: "#495057", backgroundColor: "#f8f9fa", padding: "8px", borderRadius: "4px", textAlign: "left", overflow: "auto", maxHeight: "100px" } }, this.state.error.message) ), i.createElement( "div", { style: { display: "flex", gap: "12px" } }, i.createElement("button", { onClick: this.handleRetry, style: { backgroundColor: "#007bff", color: "white", border: "none", padding: "8px 16px", borderRadius: "4px", cursor: "pointer", fontSize: "14px", fontWeight: "500" } }, "Try Again"), i.createElement("button", { onClick: this.handleRetryWithDelay, style: { backgroundColor: "#28a745", color: "white", border: "none", padding: "8px 16px", borderRadius: "4px", cursor: "pointer", fontSize: "14px", fontWeight: "500" } }, "Retry with Delay") ) ) : this.props.children; } } const ve = "https://cdn.jsdelivr.net/npm/vanta@latest/dist/", F = /* @__PURE__ */ new Set(), x = /* @__PURE__ */ new Map(), Ce = async (e) => { if (F.has(e)) { const t = window.VANTA, n = t && t[e.toUpperCase()]; if (n) return n; } if (x.has(e)) return x.get(e); const r = B(e); x.set(e, r); try { const t = await r; return F.add(e), x.delete(e), t; } catch (t) { throw x.delete(e), t; } }, xe = async (e) => { try { const r = e.map(async (n) => { const a = await Ce(n); return { name: n, effect: a }; }); return (await Promise.all(r)).reduce((n, { name: a, effect: c }) => (n[a] = c, n), {}); } catch (r) { throw console.error("[CDN Vanta] Failed to load multiple effects:", r), r; } }, Me = async () => { await xe(["waves", "birds", "net", "clouds", "fog"]); }, Se = () => ({ loadedEffects: Array.from(F), loadingEffects: Array.from(x.keys()), vantaAvailable: !!window.VANTA, loadedCount: F.size, loadingCount: x.size }), Le = () => { F.clear(), x.clear(); }, $e = (e) => { if (typeof window > "u") return !1; const r = window.VANTA; return !!(r && r[e.toUpperCase()]); }, Ie = (e) => `${ve}vanta.${e}.min.js`, Ue = () => { var e, r; try { return typeof window < "u" && window.location.hostname === "localhost" || ((r = (e = globalThis.process) == null ? void 0 : e.env) == null ? void 0 : r.VANTA_PERFORMANCE_MONITORING) === "true"; } catch { return !1; } }, ce = () => typeof window < "u" && "performance" in window && "memory" in window.performance ? window.performance.memory.usedJSHeapSize : 0, Ne = () => { const e = performance.now(), r = ce(); return { startTime: e, memoryBefore: r, effectsLoaded: 0, success: !1 }; }, _e = (e, r, t, n) => { const a = performance.now(), c = a - e.startTime, f = ce(), d = f - (e.memoryBefore || 0); return { ...e, endTime: a, loadTime: c, memoryAfter: f, memoryIncrease: d, effectsLoaded: r, success: t, errorMessage: n }; }, We = (e, r) => { }, Be = (e) => { const [r, t] = C(!1), [n, a] = C(null), [c, f] = C(!1); return U(() => { let d = !0; return (async () => { if (e) { t(!0), a(null), f(!1); try { const T = await le(e); d && (T ? f(!0) : a(`Failed to load effect: ${e}`)); } catch (T) { d && a(T instanceof Error ? T.message : "Unknown error"); } finally { d && t(!1); } } })(), () => { d = !1; }; }, [e]), { isLoading: r, error: n, isLoaded: c }; }; export { je as ErrorBoundary, ye as VANTA_EFFECTS, Fe as Vanta, S as areLibrariesReady, He as clearEffectCache, Le as clearVantaCache, _e as finishPerformanceMonitoring, Pe as getAvailableEffects, ze as getCachedEffects, Ae as getCdnLibraryStatus, ce as getMemoryUsage, De as getPreloadStatus, Ee as getPreloadedP5, he as getPreloadedThree, Ie as getVantaEffectCdnUrl, Se as getVantaLoadStatus, Ue as isPerformanceMonitoringEnabled, $e as isVantaEffectAvailable, Re as loadCdnLibraries, se as loadCdnP5, ie as loadCdnThree, B as loadCdnVantaEffect, xe as loadMultipleVantaEffects, le as loadVantaEffect, Ce as loadVantaEffectFromCdn, We as logProgress, Ve as preloadAllVantaEffects, ae as preloadLibraries, Me as preloadPopularVantaEffects, ke as resetPreloadState, Ne as startPerformanceMonitoring, Be as useVantaEffect }; //# sourceMappingURL=index.es.js.map