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
JavaScript
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