aura-glass
Version:
A comprehensive glassmorphism design system for React applications with 142+ production-ready components
511 lines (508 loc) • 14.5 kB
JavaScript
import { isBrowser, safeMatchMedia, getSafeNavigator, getSafeWindow, getSafeDocument } from './env.js';
const DEFAULT_DEVICE_INFO = {
type: 'unknown',
os: 'unknown',
browser: 'Unknown',
capabilities: {
touch: false,
multiTouch: false,
hover: false,
pointer: false,
gpu: false,
webgl: false,
webgl2: false,
hardwareAcceleration: false,
highDPI: false,
vibration: false,
geolocation: false,
camera: false,
microphone: false,
speakers: false,
bluetooth: false,
usb: false
},
performance: {
memory: 4,
cores: 4,
clockSpeed: 2000,
battery: false,
network: 'unknown',
storage: 'medium',
tier: 'medium'
},
screen: {
width: 1920,
height: 1080,
pixelRatio: 1,
orientation: 'landscape',
colorDepth: 24,
refreshRate: 60,
touchScreen: false,
ppi: 96
},
input: {
keyboard: true,
mouse: true,
touch: false,
stylus: false,
gamepad: false,
microphone: false,
camera: false
}
};
// Device detection
let __cachedDeviceInfo = null;
let __lastDetectTs = 0;
const DETECT_CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
// Safely probe WebGL support with minimal overhead and explicit cleanup
const probeWebGLSupport = () => {
const doc = getSafeDocument();
if (!doc) {
return {
webgl: false,
webgl2: false,
gpu: false
};
}
const canvas = doc.createElement('canvas');
// Keep it tiny and hint low-power
canvas.width = 1;
canvas.height = 1;
const attrs = {
alpha: false,
antialias: false,
depth: false,
stencil: false,
preserveDrawingBuffer: false,
desynchronized: true,
failIfMajorPerformanceCaveat: true,
powerPreference: 'low-power'
};
let gl = null;
let gl2 = null;
try {
gl2 = canvas.getContext('webgl2', attrs) || null;
} catch {
gl2 = null;
}
if (!gl2) {
try {
gl = canvas.getContext('webgl', attrs) || canvas.getContext('experimental-webgl', attrs) || null;
} catch {
gl = null;
}
}
const webgl2 = !!gl2;
const webgl = webgl2 || !!gl;
const gpu = webgl; // If we can get a GL context, assume GPU available
// Explicitly release the context if possible
try {
const ctx = gl2 || gl;
const lose = ctx && typeof ctx.getExtension === 'function' && ctx.getExtension('WEBGL_lose_context');
if (lose && typeof lose.loseContext === 'function') {
lose.loseContext();
}
} catch {}
try {
canvas.width = 0;
canvas.height = 0;
if (canvas.parentNode) {
canvas.parentNode.removeChild(canvas);
}
} catch {}
gl = null;
gl2 = null;
return {
webgl,
webgl2,
gpu
};
};
const detectDevice = () => {
// Basic caching to avoid repeatedly creating GL contexts across mounts
const now = Date.now();
if (__cachedDeviceInfo && now - __lastDetectTs < DETECT_CACHE_TTL_MS) {
return __cachedDeviceInfo;
}
if (!isBrowser()) {
__cachedDeviceInfo = {
...DEFAULT_DEVICE_INFO
};
__lastDetectTs = now;
return __cachedDeviceInfo;
}
const navigatorRef = getSafeNavigator();
const ua = navigatorRef?.userAgent ?? '';
const platform = navigatorRef?.platform ?? '';
// Detect device type
const type = detectDeviceType(ua, platform);
// Detect OS
const os = detectOS(ua, platform);
// Detect browser
const browser = detectBrowser(ua);
// Detect capabilities
const capabilities = detectDeviceCapabilities();
// Detect performance
const performance = detectDevicePerformance();
// Detect screen info
const screen = detectScreenInfo();
// Detect input capabilities
const input = detectInputCapabilities();
const info = {
type,
os,
browser,
capabilities,
performance,
screen,
input
};
__cachedDeviceInfo = info;
__lastDetectTs = now;
return info;
};
const detectDeviceType = (ua, platform) => {
// Check for mobile devices
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(ua)) {
// Distinguish between phone and tablet
if (/iPad|Android(?=.*\bMobile\b)|Tablet|PlayBook/i.test(ua)) {
return 'tablet';
}
return 'mobile';
}
// Check for desktop
if (/Windows|Mac|Linux/i.test(platform)) {
return 'desktop';
}
return 'unknown';
};
const detectOS = (ua, platform) => {
if (/iPhone|iPad|iPod/i.test(ua)) {
return 'ios';
}
if (/Android/i.test(ua)) {
return 'android';
}
if (/Windows/i.test(platform)) {
return 'windows';
}
if (/Mac/i.test(platform)) {
return 'macos';
}
if (/Linux/i.test(platform)) {
return 'linux';
}
return 'unknown';
};
const detectBrowser = ua => {
if (ua.includes('Chrome') && !ua.includes('Edg/')) {
return 'Chrome';
}
if (ua.includes('Firefox')) {
return 'Firefox';
}
if (ua.includes('Safari') && !ua.includes('Chrome')) {
return 'Safari';
}
if (ua.includes('Edg/')) {
return 'Edge';
}
if (ua.includes('MSIE') || ua.includes('Trident')) {
return 'Internet Explorer';
}
return 'Unknown';
};
const detectDeviceCapabilities = () => {
const win = getSafeWindow();
const nav = getSafeNavigator();
const {
webgl,
webgl2,
gpu
} = probeWebGLSupport();
return {
touch: !!win && 'ontouchstart' in win,
multiTouch: (nav?.maxTouchPoints ?? 0) > 1,
hover: safeMatchMedia('(hover: hover)')?.matches ?? false,
pointer: !!win && 'PointerEvent' in win,
gpu,
webgl,
webgl2,
hardwareAcceleration: (() => {
const doc = getSafeDocument();
if (!doc) return false;
const testElement = doc.createElement('div');
testElement.style.setProperty('transform', 'translateZ(0)');
return testElement.style.getPropertyValue('transform') === 'translateZ(0)';
})(),
highDPI: (win?.devicePixelRatio ?? 1) > 1,
vibration: !!nav && 'vibrate' in nav,
geolocation: !!nav && 'geolocation' in nav,
camera: !!(nav?.mediaDevices && nav.mediaDevices.getUserMedia),
microphone: !!(nav?.mediaDevices && nav.mediaDevices.getUserMedia),
speakers: !!win && ('AudioContext' in win || 'webkitAudioContext' in win),
bluetooth: !!nav && 'bluetooth' in nav,
usb: !!nav && 'usb' in nav
};
};
// Allow manual refresh of cached device info if needed
const refreshDeviceDetection = () => {
__cachedDeviceInfo = null;
return detectDevice();
};
const detectDevicePerformance = () => {
// Estimate memory
const nav = getSafeNavigator();
const memory = nav?.deviceMemory || 4; // Default to 4GB if not available
// Get CPU cores
const cores = nav?.hardwareConcurrency || 4; // Default to 4 cores
// Estimate clock speed (rough approximation)
const clockSpeed = cores > 4 ? 3000 : cores > 2 ? 2500 : 2000;
// Check battery
const battery = !!nav && 'getBattery' in nav;
// Estimate network speed
const network = detectNetworkSpeed();
// Estimate storage
const storage = memory > 8 ? 'high' : memory > 4 ? 'medium' : 'low';
// Calculate performance tier
const tier = calculatePerformanceTier(memory, cores, network);
return {
memory,
cores,
clockSpeed,
battery,
network,
storage,
tier
};
};
const detectNetworkSpeed = () => {
const nav = getSafeNavigator();
const connection = nav?.connection;
if (!connection) return 'unknown';
const effectiveType = connection.effectiveType;
if (effectiveType === 'slow-2g' || effectiveType === '2g') {
return 'slow';
}
return 'fast';
};
const calculatePerformanceTier = (memory, cores, network) => {
const score = memory * 0.4 + cores * 0.4 + (network === 'fast' ? 2 : 1) * 0.2;
if (score >= 6) return 'ultra';
if (score >= 4) return 'high';
if (score >= 2.5) return 'medium';
return 'low';
};
const detectScreenInfo = () => {
const win = getSafeWindow();
const screen = win?.screen;
const width = screen?.width ?? DEFAULT_DEVICE_INFO.screen.width;
const height = screen?.height ?? DEFAULT_DEVICE_INFO.screen.height;
const pixelRatio = win?.devicePixelRatio ?? DEFAULT_DEVICE_INFO.screen.pixelRatio;
const orientation = width > height ? 'landscape' : 'portrait';
const colorDepth = screen?.colorDepth ?? DEFAULT_DEVICE_INFO.screen.colorDepth;
const refreshRate = 60; // Default, hard to detect accurately
// Estimate PPI (pixels per inch)
const ppi = estimatePPI(width, height, pixelRatio);
return {
width,
height,
pixelRatio,
orientation,
colorDepth,
refreshRate,
touchScreen: !!win && 'ontouchstart' in win,
ppi
};
};
const estimatePPI = (width, height, pixelRatio) => {
// Rough PPI estimation based on common device sizes
const diagonalPixels = Math.sqrt(width ** 2 + height ** 2);
const diagonalInches = diagonalPixels / (pixelRatio * 96); // Assuming 96 PPI base
return diagonalPixels / diagonalInches;
};
const detectInputCapabilities = () => {
const win = getSafeWindow();
const nav = getSafeNavigator();
return {
keyboard: true,
// Assume keyboard support
mouse: safeMatchMedia('(hover: hover)')?.matches ?? false,
touch: !!win && 'ontouchstart' in win,
stylus: (nav?.maxTouchPoints ?? 0) > 1,
// Rough approximation
gamepad: !!nav && 'getGamepads' in nav,
microphone: !!(nav?.mediaDevices && nav.mediaDevices.getUserMedia),
camera: !!(nav?.mediaDevices && nav.mediaDevices.getUserMedia)
};
};
// Performance optimization utilities
const performanceOptimizations = {
// Get optimal animation settings based on device
getOptimalAnimationSettings: device => {
const {
performance,
capabilities
} = device;
switch (performance.tier) {
case 'ultra':
return {
duration: 300,
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
useHardwareAcceleration: true,
useWebAnimations: capabilities.webgl
};
case 'high':
return {
duration: 400,
easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
useHardwareAcceleration: true,
useWebAnimations: capabilities.webgl
};
case 'medium':
return {
duration: 500,
easing: 'ease',
useHardwareAcceleration: capabilities.hardwareAcceleration,
useWebAnimations: false
};
case 'low':
return {
duration: 600,
easing: 'linear',
useHardwareAcceleration: false,
useWebAnimations: false
};
default:
return {
duration: 400,
easing: 'ease',
useHardwareAcceleration: capabilities.hardwareAcceleration,
useWebAnimations: false
};
}
},
// Get optimal rendering settings
getOptimalRenderingSettings: device => {
const {
performance,
screen
} = device;
return {
useCanvas: performance.tier === 'ultra' || performance.tier === 'high',
useWebGL: device.capabilities.webgl && performance.tier !== 'low',
pixelRatio: screen.pixelRatio > 2 ? 2 : screen.pixelRatio,
maxTextureSize: performance.tier === 'ultra' ? 4096 : 2048,
antialiasing: performance.tier !== 'low',
shadows: performance.tier === 'ultra' || performance.tier === 'high',
particles: performance.tier === 'ultra' ? 1000 : performance.tier === 'high' ? 500 : 100
};
},
// Get optimal memory settings
getOptimalMemorySettings: device => {
const {
performance
} = device;
return {
maxCacheSize: performance.memory * 1024 * 1024 * 0.1,
// 10% of available memory
textureCacheSize: performance.tier === 'ultra' ? 100 : 50,
geometryCacheSize: performance.tier === 'ultra' ? 50 : 25,
shaderCacheSize: performance.tier === 'ultra' ? 20 : 10
};
},
// Get optimal network settings
getOptimalNetworkSettings: device => {
const {
performance
} = device;
return {
preloadAssets: performance.network !== 'slow',
compressTextures: performance.network === 'slow',
useCDN: performance.network === 'fast',
cacheStrategy: performance.network === 'fast' ? 'aggressive' : 'conservative'
};
}
};
// Device-specific optimizations
const deviceOptimizations = {
// Mobile optimizations
mobile: {
reduceMotion: () => ({
prefersReducedMotion: safeMatchMedia('(prefers-reduced-motion: reduce)')?.matches ?? true,
disableParallax: true,
simplifyAnimations: true,
reduceParticleCount: true
}),
optimizeTouch: () => ({
preventZoom: true,
improveTapTargets: true,
disableDoubleTapZoom: true,
optimizeScroll: true
}),
optimizeBattery: () => ({
reduceFrameRate: true,
disableNonEssentialAnimations: true,
optimizeRendering: true
})
},
// Tablet optimizations
tablet: {
hybridInput: () => ({
supportTouchAndMouse: true,
optimizeForBoth: true,
adaptiveUI: true
})
},
// Desktop optimizations
desktop: {
maximizePerformance: () => ({
useAdvancedShaders: true,
enablePostProcessing: true,
optimizeForGPU: true
})
}
};
// Adaptive rendering utilities
const adaptiveRendering = {
// Adjust quality based on performance
adjustQualityForPerformance: (currentFPS, targetFPS = 60, currentQuality) => {
const fpsRatio = currentFPS / targetFPS;
if (fpsRatio < 0.8) {
// Reduce quality if FPS is too low
return Math.max(0.1, currentQuality * 0.8);
} else if (fpsRatio > 1.2) {
// Increase quality if FPS is good
return Math.min(1, currentQuality * 1.1);
}
return currentQuality;
},
// Adjust detail level based on device
getDetailLevel: device => {
switch (device.performance.tier) {
case 'ultra':
return device.screen.pixelRatio > 2 ? 'ultra' : 'high';
case 'high':
return 'high';
case 'medium':
return 'medium';
case 'low':
return 'low';
default:
return 'medium';
}
},
// Get optimal texture size
getOptimalTextureSize: (device, baseSize) => {
const detailLevel = adaptiveRendering.getDetailLevel(device);
const multipliers = {
ultra: 1,
high: 0.8,
medium: 0.6,
low: 0.4
};
return Math.floor(baseSize * multipliers[detailLevel]);
}
};
export { DEFAULT_DEVICE_INFO, adaptiveRendering, detectDevice as default, detectDevice, deviceOptimizations, performanceOptimizations, refreshDeviceDetection };
//# sourceMappingURL=deviceCapabilities.js.map