// src/components/VantaHaloBackground.jsx 'use client'; import React, { useRef, useEffect, memo } from 'react' import dynamic from 'next/dynamic' // Remove direct import of THREE to avoid SSR issues // import * as THREE from 'three' interface VantaHaloBackgroundProps { onLoaded?: () => void; } // Add VANTA to the window type declare global { interface Window { VANTA: any; THREE: any; // Add THREE to window type } } // 预加载 Vanta 脚本 const preloadVantaScript = () => { const link = document.createElement('link') link.rel = 'preload' link.as = 'script' link.href = '/js/three.min.js' document.head.appendChild(link) const link2 = document.createElement('link') link2.rel = 'preload' link2.as = 'script' link2.href = '/lib/vanta.halo.min.js' document.head.appendChild(link2) } // 使用 React.memo 来避免不必要的重渲染 const VantaHaloBackground = memo(({ onLoaded }: VantaHaloBackgroundProps) => { const vantaRef = useRef(null) const effectInstance = useRef(null) const frameId = useRef() useEffect(() => { let canceled = false preloadVantaScript() const loadVanta = async () => { try { // Dynamically load the script instead of importing const threeScript = document.createElement('script') threeScript.src = '/js/three.min.js' document.body.appendChild(threeScript) threeScript.onload = () => { const vantaScript = document.createElement('script') vantaScript.src = '/lib/vanta.halo.min.js' document.body.appendChild(vantaScript) vantaScript.onload = () => { if (canceled || !vantaRef.current || effectInstance.current) return // 使用 requestAnimationFrame 来控制动画帧率 const animate = () => { if (effectInstance.current) { effectInstance.current.frameRequestId = requestAnimationFrame(animate) } } // Access VANTA from the window object after script loads if (window.VANTA && window.VANTA.HALO) { effectInstance.current = window.VANTA.HALO({ el: vantaRef.current, THREE: window.THREE, // Use THREE from window instead of import mouseControls: true, touchControls: true, gyroControls: false, scale: 1.0, scaleMobile: 1.0, amplitudeFactor: 1.5, ringFactor: 1.3, size: 1.2, minHeight: 200.00, minWidth: 200.00, // 优化渲染性能的参数 fps: 30, // 限制帧率 renderCacheSize: 4, // 缓存大小 }) frameId.current = requestAnimationFrame(animate) // 通知加载完成 if (onLoaded) { onLoaded(); } } } } } catch (error) { console.error('Failed to load Vanta effect:', error) } } // 使用 requestIdleCallback 在浏览器空闲时初始化 if ('requestIdleCallback' in window) { requestIdleCallback(() => loadVanta(), { timeout: 2000 }) } else { setTimeout(loadVanta, 100) } return () => { canceled = true if (frameId.current) { cancelAnimationFrame(frameId.current) } if (effectInstance.current) { effectInstance.current.destroy() effectInstance.current = null } } }, [onLoaded]) return (
) }) VantaHaloBackground.displayName = 'VantaHaloBackground' // Export with noSSR to prevent server-side rendering export default dynamic(() => Promise.resolve(VantaHaloBackground), { ssr: false })