monacousa-portal/utils/mobile-safari-utils.ts

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);
};
}