From 0f6088ac2a6a27a9fdf1706d40afef2fa6592a05 Mon Sep 17 00:00:00 2001 From: wheattoast11 Date: Sat, 30 Nov 2024 15:43:48 -0600 Subject: [PATCH] update performance --- src/App.jsx | 35 +++++++++++------- src/components/Canvas.jsx | 75 ++++++++++++++++----------------------- src/main.jsx | 5 +++ src/three/Scene.js | 47 ++++++++++++++++++------ src/utils/performance.js | 33 +++++++++++++++++ vite.config.js | 11 +++--- 6 files changed, 135 insertions(+), 71 deletions(-) create mode 100644 src/utils/performance.js diff --git a/src/App.jsx b/src/App.jsx index aaafdbf..f7ee7e8 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,10 @@ import React, { Suspense, lazy, useEffect } from 'react'; -import { HashRouter as Router, Routes, Route } from 'react-router-dom'; +import { + createBrowserRouter, + RouterProvider, + UNSAFE_DataRouterContext, + UNSAFE_DataRouterStateContext +} from 'react-router-dom'; import LoadingState from './components/LoadingState'; import Canvas from './components/Canvas'; import ErrorBoundary from './components/ErrorBoundary'; @@ -9,6 +14,22 @@ import reportWebVitals from './utils/reportWebVitals'; const Home = lazy(() => import('./pages/Home')); const Manifesto = lazy(() => import('./pages/Manifesto')); +const router = createBrowserRouter([ + { + path: "/", + element: , + }, + { + path: "/manifesto", + element: , + } +], { + future: { + v7_startTransition: true, + v7_relativeSplatPath: true + } +}); + function App() { useEffect(() => { if ('performance' in window) { @@ -19,17 +40,7 @@ function App() { return ( - -
- - }> - - } /> - } /> - - -
-
+
); } diff --git a/src/components/Canvas.jsx b/src/components/Canvas.jsx index 354425e..679973a 100644 --- a/src/components/Canvas.jsx +++ b/src/components/Canvas.jsx @@ -1,61 +1,46 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef, useState, useCallback } from 'react'; import { Scene } from '../three/Scene'; function Canvas() { const containerRef = useRef(null); + const sceneRef = useRef(null); const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); - const [scene, setScene] = useState(null); - // Handle resize - useEffect(() => { - const handleResize = () => { - if (containerRef.current) { - setDimensions({ - width: containerRef.current.clientWidth, - height: containerRef.current.clientHeight - }); - } - }; - - // Initial size - handleResize(); - - window.addEventListener('resize', handleResize); - return () => window.removeEventListener('resize', handleResize); + const handleResize = useCallback(() => { + if (containerRef.current) { + const { clientWidth, clientHeight } = containerRef.current; + setDimensions({ width: clientWidth, height: clientHeight }); + } }, []); - // Initialize scene useEffect(() => { - if (!containerRef.current || dimensions.width === 0 || dimensions.height === 0) { - return; + const resizeObserver = new ResizeObserver(handleResize); + if (containerRef.current) { + resizeObserver.observe(containerRef.current); } + return () => resizeObserver.disconnect(); + }, [handleResize]); - try { - const newScene = new Scene(containerRef.current, dimensions); - setScene(prev => { - if (prev) { - prev.dispose(); - } - return newScene; - }); - - // Start animation - const animate = () => { - if (newScene) { - newScene.animate(); - } - requestAnimationFrame(animate); - }; - animate(); + useEffect(() => { + if (!containerRef.current || dimensions.width === 0) return; - return () => { - if (newScene) { - newScene.dispose(); - } - }; + try { + if (!sceneRef.current) { + sceneRef.current = new Scene(containerRef.current, dimensions); + sceneRef.current.animate(0); + } else { + sceneRef.current.resize(dimensions); + } } catch (error) { - console.error('Error initializing scene:', error); + console.error('Scene initialization error:', error); } + + return () => { + if (sceneRef.current) { + sceneRef.current.dispose(); + sceneRef.current = null; + } + }; }, [dimensions]); return ( @@ -76,4 +61,4 @@ function Canvas() { ); } -export default Canvas; \ No newline at end of file +export default React.memo(Canvas); \ No newline at end of file diff --git a/src/main.jsx b/src/main.jsx index 98a53b9..892c32b 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -2,6 +2,11 @@ import React from 'react'; import { createRoot } from 'react-dom/client'; import App from './App'; import '../style.css'; +import { initPerformanceMonitoring } from './utils/performance'; + +if (process.env.NODE_ENV === 'development') { + initPerformanceMonitoring(); +} // Create React root and render App const root = createRoot(document.getElementById('root')); diff --git a/src/three/Scene.js b/src/three/Scene.js index ff1ce14..0c04ce7 100644 --- a/src/three/Scene.js +++ b/src/three/Scene.js @@ -23,6 +23,12 @@ export class Scene { this.mouse = new THREE.Vector2(); this.targetRotation = new THREE.Vector2(); + this.lastTime = 0; + this.frameInterval = 1000 / 30; // Target 30 FPS + + this.frames = 0; + this.lastFpsUpdate = 0; + this.init(); this.setupLights(); this.setupPostProcessing(); @@ -85,20 +91,41 @@ export class Scene { }); } - animate() { - requestAnimationFrame(this.animate.bind(this)); - this.time += 0.001; + animate(currentTime) { + // Skip frames to maintain target FPS + if (currentTime - this.lastTime < this.frameInterval) { + requestAnimationFrame(this.animate.bind(this)); + return; + } + + // Calculate delta time + const deltaTime = currentTime - this.lastTime; + this.lastTime = currentTime; - // Smooth camera rotation - this.camera.rotation.x += (this.targetRotation.x - this.camera.rotation.x) * 0.05; - this.camera.rotation.y += (this.targetRotation.y - this.camera.rotation.y) * 0.05; + // FPS monitoring + this.frames++; + if (currentTime - this.lastFpsUpdate > 1000) { + console.debug('FPS:', this.frames); + this.frames = 0; + this.lastFpsUpdate = currentTime; + } - // Update components - this.flowField.update(this.time); - this.spheres.update(this.time); + // Batch geometry updates + this.flowField.update(this.time, deltaTime); + this.spheres.update(this.time, deltaTime); + + // Use lower quality settings on mobile + if (window.innerWidth < 768) { + this.renderer.setPixelRatio(1); + this.composer.passes.forEach(pass => { + if (pass.uniforms?.resolution) { + pass.uniforms.resolution.value.set(window.innerWidth/2, window.innerHeight/2); + } + }); + } - // Render with post-processing this.composer.render(); + requestAnimationFrame(this.animate.bind(this)); } dispose() { diff --git a/src/utils/performance.js b/src/utils/performance.js new file mode 100644 index 0000000..5d03634 --- /dev/null +++ b/src/utils/performance.js @@ -0,0 +1,33 @@ +export const initPerformanceMonitoring = () => { + // Create PerformanceObserver + const perfObserver = new PerformanceObserver((list) => { + list.getEntries().forEach((entry) => { + // Log only significant performance issues + if (entry.duration > 50) { + console.warn(`Performance entry: ${entry.name} took ${entry.duration}ms`); + } + }); + }); + + // Observe paint timing + perfObserver.observe({ entryTypes: ['paint', 'largest-contentful-paint'] }); + + // Monitor long tasks + const longTaskObserver = new PerformanceObserver((list) => { + list.getEntries().forEach((entry) => { + console.warn(`Long task detected: ${entry.duration}ms`); + }); + }); + + longTaskObserver.observe({ entryTypes: ['longtask'] }); + + // Monitor memory usage + if (performance.memory) { + setInterval(() => { + const { usedJSHeapSize, totalJSHeapSize } = performance.memory; + const usedMB = Math.round(usedJSHeapSize / 1024 / 1024); + const totalMB = Math.round(totalJSHeapSize / 1024 / 1024); + console.debug(`Memory usage: ${usedMB}MB / ${totalMB}MB`); + }, 10000); + } +}; \ No newline at end of file diff --git a/vite.config.js b/vite.config.js index 2cc1d15..822504f 100644 --- a/vite.config.js +++ b/vite.config.js @@ -11,7 +11,7 @@ export default defineConfig({ manualChunks: { 'vendor': ['react', 'react-dom', 'react-router-dom'], 'three': ['three'], - 'animation': ['framer-motion', 'pts'] + 'animation': ['framer-motion'] } } }, @@ -20,8 +20,8 @@ export default defineConfig({ minify: 'terser', terserOptions: { compress: { - drop_console: false, - drop_debugger: false + drop_console: true, + drop_debugger: true } } }, @@ -29,7 +29,10 @@ export default defineConfig({ extensions: ['.js', '.jsx'] }, optimizeDeps: { - include: ['three', 'framer-motion', 'pts'] + include: ['three', 'framer-motion'], + esbuildOptions: { + target: 'esnext' + } }, server: { headers: {