UNPKG

aura-glass

Version:

A comprehensive glassmorphism design system for React applications with 142+ production-ready components

318 lines (315 loc) 10.8 kB
'use client'; import { useState, useRef, useCallback, useEffect } from 'react'; import { PerformanceMonitor, createCleanupManager, getAdaptivePerformanceConfig } from '../core/mixins/performanceMixins.js'; import { detectDevice } from '../utils/deviceCapabilities.js'; /** * Enhanced performance monitoring and optimization hook */ function useEnhancedPerformance(options = {}) { const { enableMetrics = true, metricsInterval = 5000, adaptiveMode = true, onPerformanceChange } = options; const [state, setState] = useState({ isOptimized: false, performanceMode: 'balanced', metrics: null, isLoading: true, error: null }); const performanceMonitor = useRef(PerformanceMonitor.getInstance()); const metricsIntervalRef = useRef(); const frameCountRef = useRef(0); const frameLoopIdRef = useRef(null); const cleanupManager = useRef(createCleanupManager()); const hasCSSSupports = typeof CSS !== 'undefined' && typeof CSS.supports === 'function'; // Measure frame rate const measureFrameRate = useCallback(() => { if (typeof window === 'undefined' || typeof requestAnimationFrame === 'undefined') { frameCountRef.current = 60; return; } if (frameLoopIdRef.current !== null) { cancelAnimationFrame(frameLoopIdRef.current); frameLoopIdRef.current = null; } let frameCount = 0; let startTime = typeof performance !== 'undefined' && typeof performance.now === 'function' ? performance.now() : Date.now(); let isActive = true; const countFrame = () => { if (!isActive) { return; } frameCount++; const now = typeof performance !== 'undefined' && typeof performance.now === 'function' ? performance.now() : Date.now(); if (now - startTime >= 1000) { frameCountRef.current = frameCount; frameCount = 0; startTime = now; } frameLoopIdRef.current = requestAnimationFrame(countFrame); }; frameLoopIdRef.current = requestAnimationFrame(countFrame); cleanupManager.current.add(() => { isActive = false; if (frameLoopIdRef.current !== null && typeof cancelAnimationFrame === 'function') { cancelAnimationFrame(frameLoopIdRef.current); frameLoopIdRef.current = null; } }); }, [cleanupManager]); // Collect performance metrics const collectMetrics = useCallback(async () => { if (!enableMetrics || typeof window === 'undefined') return null; try { // Memory usage (if available) const memoryInfo = performance.memory; const memoryUsage = memoryInfo ? memoryInfo.usedJSHeapSize / memoryInfo.jsHeapSizeLimit : 0; // Network speed const connection = navigator.connection; const networkSpeed = connection?.effectiveType || 'unknown'; // Device capabilities (cached, avoids creating WebGL contexts repeatedly) const deviceInfo = detectDevice(); const supportsBackdropFilter = hasCSSSupports && (CSS.supports('backdrop-filter', 'blur(1px)') || CSS.supports('-webkit-backdrop-filter', 'blur(1px)')); const deviceCapabilities = { supportsGPU: deviceInfo.capabilities.webgl, supportsBackdropFilter, devicePixelRatio: typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1 }; // Render time (average from performance monitor) const renderTime = performanceMonitor.current.getAverageMetric('render') || 0; return { renderTime, memoryUsage, frameRate: frameCountRef.current, networkSpeed, deviceCapabilities }; } catch (error) { console.warn('Failed to collect performance metrics:', error); return null; } }, [enableMetrics]); // Optimize performance based on current metrics const optimizeForDevice = useCallback(() => { const config = getAdaptivePerformanceConfig(); setState(prev => ({ ...prev, performanceMode: config.mode || 'balanced', isOptimized: true })); }, []); // Start performance measurement const startMeasure = useCallback(name => { performanceMonitor.current.startMeasure(name); }, []); // End performance measurement const endMeasure = useCallback(name => { return performanceMonitor.current.endMeasure(name); }, []); // Clear metrics const clearMetrics = useCallback(() => { performanceMonitor.current.clearMetrics(); frameCountRef.current = 0; }, []); // Initialize performance monitoring useEffect(() => { const initialize = async () => { try { setState(prev => ({ ...prev, isLoading: true, error: null })); // Start frame rate monitoring if (enableMetrics) { measureFrameRate(); } // Adaptive optimization if (adaptiveMode) { optimizeForDevice(); } // Set up metrics collection interval if (enableMetrics && metricsInterval > 0) { const intervalId = setInterval(async () => { const metrics = await collectMetrics(); if (metrics) { setState(prev => { const newState = { ...prev, metrics }; onPerformanceChange?.(newState); return newState; }); } }, metricsInterval); metricsIntervalRef.current = intervalId; cleanupManager.current.add(() => clearInterval(intervalId)); } // Initial metrics collection const initialMetrics = await collectMetrics(); setState(prev => ({ ...prev, metrics: initialMetrics, isLoading: false })); } catch (error) { setState(prev => ({ ...prev, error: error instanceof Error ? error.message : 'Performance initialization failed', isLoading: false })); } }; initialize(); return () => { cleanupManager.current.cleanup(); if (metricsIntervalRef.current) { clearInterval(metricsIntervalRef.current); } if (frameLoopIdRef.current !== null && typeof cancelAnimationFrame === 'function') { cancelAnimationFrame(frameLoopIdRef.current); frameLoopIdRef.current = null; } }; }, [enableMetrics, metricsInterval, adaptiveMode, measureFrameRate, collectMetrics, optimizeForDevice, onPerformanceChange]); return { ...state, startMeasure, endMeasure, optimizeForDevice, clearMetrics }; } /** * Hook for performance-aware rendering */ function usePerformanceAwareRendering(data, options = {}) { const { itemsPerPage = 50, enableVirtualization = true, performanceThreshold = 100 } = options; const [currentPage, setCurrentPage] = useState(0); const { metrics } = useEnhancedPerformance(); const shouldVirtualize = enableVirtualization && ((data?.length || 0) > performanceThreshold || (metrics?.frameRate || 60) < 30 || (metrics?.memoryUsage || 0) > 0.8); const totalPages = Math.ceil((data?.length || 0) / itemsPerPage); const visibleData = shouldVirtualize ? data?.slice(currentPage * itemsPerPage, (currentPage + 1) * itemsPerPage) : data; const setPage = useCallback(page => { setCurrentPage(Math.max(0, Math.min(page, totalPages - 1))); }, [totalPages]); return { visibleData, shouldVirtualize, currentPage, totalPages, setPage }; } /** * Hook for lazy loading with performance awareness */ function usePerformanceLazyLoading(enabled = true, options = {}) { const { threshold = 0.1, rootMargin = '50px', performanceMode } = options; const [isIntersecting, setIsIntersecting] = useState(false); const ref = useRef(null); const { performanceMode: detectedMode } = useEnhancedPerformance(); const effectiveMode = performanceMode || detectedMode; const shouldLoad = !enabled || isIntersecting || effectiveMode === 'high'; useEffect(() => { if (!enabled || !ref.current || typeof IntersectionObserver === 'undefined') { setIsIntersecting(true); return; } const observer = new IntersectionObserver(([entry]) => { setIsIntersecting(entry.isIntersecting); }, { threshold: effectiveMode === 'low' ? 0.5 : threshold, rootMargin: effectiveMode === 'low' ? '0px' : rootMargin }); observer.observe(ref.current); return () => { observer.disconnect(); }; }, [enabled, threshold, rootMargin, effectiveMode]); return { ref, isIntersecting, shouldLoad }; } /** * Hook for adaptive image loading based on performance */ function useAdaptiveImageLoading(src, options = {}) { const { lowQualitySrc, webpSrc, avifSrc } = options; const [currentSrc, setCurrentSrc] = useState(lowQualitySrc || src); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const { metrics, performanceMode } = useEnhancedPerformance(); const supportsCSS = typeof CSS !== 'undefined' && typeof CSS.supports === 'function'; useEffect(() => { const loadImage = async () => { setIsLoading(true); setError(null); try { // Determine best image format based on performance and support let targetSrc = src; if (performanceMode === 'high' && metrics?.networkSpeed === '4g' && supportsCSS) { // Use best quality format available if (avifSrc && CSS.supports('image-format', 'avif')) { targetSrc = avifSrc; } else if (webpSrc && CSS.supports('image-format', 'webp')) { targetSrc = webpSrc; } } else if (performanceMode === 'low' || metrics?.networkSpeed === '2g') { // Use low quality version if available if (lowQualitySrc) { targetSrc = lowQualitySrc; } } // Preload image const img = new Image(); img.onload = () => { setCurrentSrc(targetSrc); setIsLoading(false); }; img.onerror = () => { setError('Failed to load image'); setCurrentSrc(src); // Fallback to original setIsLoading(false); }; img.src = targetSrc; } catch (err) { setError(err instanceof Error ? err.message : 'Image loading failed'); setIsLoading(false); } }; loadImage(); }, [src, lowQualitySrc, webpSrc, avifSrc, performanceMode, metrics?.networkSpeed, supportsCSS]); return { currentSrc, isLoading, error }; } export { useAdaptiveImageLoading, useEnhancedPerformance, usePerformanceAwareRendering, usePerformanceLazyLoading }; //# sourceMappingURL=useEnhancedPerformance.js.map