172 lines
4.3 KiB
TypeScript
172 lines
4.3 KiB
TypeScript
/**
|
|
* Mobile Safari Detection and Optimization Utilities
|
|
* Handles Safari-specific issues and performance optimizations
|
|
*/
|
|
|
|
export interface DeviceInfo {
|
|
isMobile: boolean;
|
|
isSafari: boolean;
|
|
isMobileSafari: boolean;
|
|
isIOS: boolean;
|
|
safariVersion?: number;
|
|
}
|
|
|
|
/**
|
|
* Detect device and browser information
|
|
*/
|
|
export function getDeviceInfo(): DeviceInfo {
|
|
if (typeof window === 'undefined') {
|
|
return {
|
|
isMobile: false,
|
|
isSafari: false,
|
|
isMobileSafari: false,
|
|
isIOS: false
|
|
};
|
|
}
|
|
|
|
const userAgent = navigator.userAgent;
|
|
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(userAgent);
|
|
const isIOS = /iPad|iPhone|iPod/.test(userAgent);
|
|
const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent);
|
|
const isMobileSafari = isIOS && isSafari;
|
|
|
|
// Extract Safari version if possible
|
|
let safariVersion: number | undefined;
|
|
if (isSafari) {
|
|
const match = userAgent.match(/Version\/(\d+)/);
|
|
if (match) {
|
|
safariVersion = parseInt(match[1]);
|
|
}
|
|
}
|
|
|
|
return {
|
|
isMobile,
|
|
isSafari,
|
|
isMobileSafari,
|
|
isIOS,
|
|
safariVersion
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Check if the device needs performance optimizations
|
|
*/
|
|
export function needsPerformanceOptimization(): boolean {
|
|
const { isMobileSafari, isMobile } = getDeviceInfo();
|
|
return isMobileSafari || isMobile;
|
|
}
|
|
|
|
/**
|
|
* Check if backdrop-filter should be disabled
|
|
*/
|
|
export function shouldDisableBackdropFilter(): boolean {
|
|
const { isMobileSafari, safariVersion } = getDeviceInfo();
|
|
// Disable backdrop-filter on mobile Safari or older Safari versions
|
|
return isMobileSafari || Boolean(safariVersion && safariVersion < 16);
|
|
}
|
|
|
|
/**
|
|
* Get optimized CSS class names based on device
|
|
*/
|
|
export function getOptimizedClasses(): string[] {
|
|
const classes: string[] = [];
|
|
const { isMobile, isMobileSafari, isIOS } = getDeviceInfo();
|
|
|
|
if (isMobile) classes.push('is-mobile');
|
|
if (isMobileSafari) classes.push('is-mobile-safari');
|
|
if (isIOS) classes.push('is-ios');
|
|
if (needsPerformanceOptimization()) classes.push('performance-mode');
|
|
|
|
return classes;
|
|
}
|
|
|
|
/**
|
|
* Optimized viewport height for mobile Safari
|
|
*/
|
|
export function getOptimizedViewportHeight(): string {
|
|
const { isMobileSafari, safariVersion } = getDeviceInfo();
|
|
|
|
if (isMobileSafari) {
|
|
// Use 100vh for older Safari, -webkit-fill-available for newer
|
|
return safariVersion && safariVersion >= 15 ? '-webkit-fill-available' : '100vh';
|
|
}
|
|
|
|
// Use dvh for modern browsers, vh as fallback
|
|
return '100vh';
|
|
}
|
|
|
|
/**
|
|
* Apply mobile Safari specific fixes
|
|
*/
|
|
export function applyMobileSafariFixes(): void {
|
|
if (typeof window === 'undefined') return;
|
|
|
|
const { isMobileSafari } = getDeviceInfo();
|
|
if (!isMobileSafari) return;
|
|
|
|
// Fix viewport height issues
|
|
const setViewportHeight = () => {
|
|
const vh = window.innerHeight * 0.01;
|
|
document.documentElement.style.setProperty('--vh', `${vh}px`);
|
|
};
|
|
|
|
// Set initial value
|
|
setViewportHeight();
|
|
|
|
// Update on resize (debounced)
|
|
let resizeTimeout: NodeJS.Timeout;
|
|
window.addEventListener('resize', () => {
|
|
clearTimeout(resizeTimeout);
|
|
resizeTimeout = setTimeout(setViewportHeight, 100);
|
|
});
|
|
|
|
// Add performance optimization classes
|
|
document.documentElement.classList.add(...getOptimizedClasses());
|
|
}
|
|
|
|
/**
|
|
* Throttle function for performance
|
|
*/
|
|
export function throttle<T extends (...args: any[]) => void>(
|
|
func: T,
|
|
wait: number
|
|
): (...args: Parameters<T>) => void {
|
|
let timeout: NodeJS.Timeout | null = null;
|
|
let previous = 0;
|
|
|
|
return function(this: any, ...args: Parameters<T>) {
|
|
const now = Date.now();
|
|
const remaining = wait - (now - previous);
|
|
|
|
if (remaining <= 0 || remaining > wait) {
|
|
if (timeout) {
|
|
clearTimeout(timeout);
|
|
timeout = null;
|
|
}
|
|
previous = now;
|
|
func.apply(this, args);
|
|
} else if (!timeout) {
|
|
timeout = setTimeout(() => {
|
|
previous = Date.now();
|
|
timeout = null;
|
|
func.apply(this, args);
|
|
}, remaining);
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Debounce function for performance
|
|
*/
|
|
export function debounce<T extends (...args: any[]) => void>(
|
|
func: T,
|
|
wait: number
|
|
): (...args: Parameters<T>) => void {
|
|
let timeout: NodeJS.Timeout;
|
|
|
|
return function(this: any, ...args: Parameters<T>) {
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(() => func.apply(this, args), wait);
|
|
};
|
|
}
|