aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
318 lines (315 loc) • 10.8 kB
JavaScript
'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