forked from 77media/video-flow
142 lines
3.9 KiB
TypeScript
142 lines
3.9 KiB
TypeScript
// 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<HTMLDivElement>(null)
|
|
const effectInstance = useRef<any>(null)
|
|
const frameId = useRef<number>()
|
|
|
|
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 (
|
|
<div
|
|
ref={vantaRef}
|
|
style={{
|
|
width: '61.8vw',
|
|
height: '100vh',
|
|
position: 'fixed',
|
|
top: 0,
|
|
left: 0,
|
|
zIndex: -1,
|
|
willChange: 'transform', // 优化图层合成
|
|
transform: 'translateZ(0)', // 启用硬件加速
|
|
}}
|
|
/>
|
|
)
|
|
})
|
|
|
|
VantaHaloBackground.displayName = 'VantaHaloBackground'
|
|
|
|
// Export with noSSR to prevent server-side rendering
|
|
export default dynamic(() => Promise.resolve(VantaHaloBackground), { ssr: false })
|