PatternsMobile & PWA
Adaptive Loading
Adjust content quality and features based on network speed and device capabilities.
The Problem
Loading high-quality assets on slow networks creates poor user experience:
- Users on 2G/3G wait too long for content
- Mobile data plans get consumed unnecessarily
- Features that require fast connections fail or timeout
- One-size-fits-all approach ignores network reality
Solution
type NetworkSpeed = 'slow-2g' | '2g' | '3g' | '4g' | 'unknown';
interface NetworkInfo {
effectiveType: NetworkSpeed;
downlink?: number; // Mbps
rtt?: number; // Round-trip time in ms
saveData?: boolean; // User enabled data saver
}
interface NetworkInformation {
effectiveType?: NetworkSpeed;
downlink?: number;
rtt?: number;
saveData?: boolean;
addEventListener(type: string, listener: EventListener): void;
removeEventListener(type: string, listener: EventListener): void;
}
class AdaptiveLoader {
/**
* Gets current network information
*/
static getNetworkInfo(): NetworkInfo {
const nav = navigator as Navigator & {
connection?: NetworkInformation;
mozConnection?: NetworkInformation;
webkitConnection?: NetworkInformation;
};
const connection = nav.connection || nav.mozConnection || nav.webkitConnection;
if (!connection) {
return { effectiveType: 'unknown' };
}
return {
effectiveType: connection.effectiveType || 'unknown',
downlink: connection.downlink,
rtt: connection.rtt,
saveData: connection.saveData || false
};
}
/**
* Determines if network is slow
*/
static isSlowNetwork(): boolean {
const { effectiveType, saveData } = this.getNetworkInfo();
// User explicitly enabled data saver
if (saveData) return true;
// 2G networks are considered slow
return effectiveType === 'slow-2g' || effectiveType === '2g';
}
/**
* Returns appropriate image quality based on network
*/
static getImageQuality(): 'low' | 'medium' | 'high' {
const { effectiveType } = this.getNetworkInfo();
switch (effectiveType) {
case 'slow-2g':
case '2g':
return 'low';
case '3g':
return 'medium';
case '4g':
default:
return 'high';
}
}
/**
* Selects appropriate image source based on network
*/
static selectImageSource(sources: {
low: string;
medium: string;
high: string;
}): string {
const quality = this.getImageQuality();
return sources[quality];
}
/**
* Decides whether to preload resources
*/
static shouldPreload(): boolean {
const { effectiveType, saveData } = this.getNetworkInfo();
if (saveData) return false;
// Only preload on fast networks
return effectiveType === '4g';
}
/**
* Adjusts video quality based on network
*/
static getVideoQuality(): '360p' | '720p' | '1080p' {
const { effectiveType } = this.getNetworkInfo();
switch (effectiveType) {
case 'slow-2g':
case '2g':
return '360p';
case '3g':
return '720p';
case '4g':
default:
return '1080p';
}
}
/**
* Listens for network changes
*/
static onNetworkChange(callback: (info: NetworkInfo) => void): () => void {
const nav = navigator as Navigator & {
connection?: NetworkInformation;
mozConnection?: NetworkInformation;
webkitConnection?: NetworkInformation;
};
const connection = nav.connection || nav.mozConnection || nav.webkitConnection;
if (!connection) {
return () => {}; // No-op cleanup
}
const handler = () => {
callback(this.getNetworkInfo());
};
connection.addEventListener('change', handler);
return () => {
connection.removeEventListener('change', handler);
};
}
}
// Usage: Load images adaptively
function loadAdaptiveImage(imageElement: HTMLImageElement) {
const sources = {
low: '/images/photo-360w.jpg',
medium: '/images/photo-720w.jpg',
high: '/images/photo-1080w.jpg'
};
const selectedSrc = AdaptiveLoader.selectImageSource(sources);
imageElement.src = selectedSrc;
console.log(`Loading ${AdaptiveLoader.getImageQuality()} quality image`);
}
// Usage: Conditional feature loading
async function loadApp() {
const isSlowNetwork = AdaptiveLoader.isSlowNetwork();
if (isSlowNetwork) {
// Load minimal features only
console.log('Slow network detected - loading lite version');
await loadLiteVersion();
} else {
// Load full experience
console.log('Fast network - loading full version');
await loadFullVersion();
}
}
async function loadLiteVersion() {
// Skip heavy animations
document.body.classList.add('reduced-motion');
// Load essential JS only
await import('./core.js');
}
async function loadFullVersion() {
// Load all features
await Promise.all([
import('./core.js'),
import('./animations.js'),
import('./analytics.js')
]);
}
// Usage: React to network changes
function setupNetworkMonitoring() {
const cleanup = AdaptiveLoader.onNetworkChange((info) => {
console.log('Network changed:', info.effectiveType);
if (info.effectiveType === '2g' || info.effectiveType === 'slow-2g') {
// Disable auto-play videos
document.querySelectorAll('video[autoplay]').forEach((video) => {
(video as HTMLVideoElement).pause();
});
}
});
// Cleanup on page unload
window.addEventListener('beforeunload', cleanup);
}
// Usage: Adaptive API calls
async function fetchData(endpoint: string) {
const shouldPreload = AdaptiveLoader.shouldPreload();
if (!shouldPreload) {
// Fetch only when needed (lazy loading)
return null;
}
// Preload on fast networks
const response = await fetch(endpoint);
return response.json();
}Network Speed Reference
| Type | Speed | Use Case |
|---|---|---|
| slow-2g | Less than 50 Kbps | Text only, minimal images |
| 2g | ~70 Kbps | Low-res images, no videos |
| 3g | ~700 Kbps | Medium images, compressed videos |
| 4g | 10+ Mbps | High-quality everything |
Performance Note
Benefit: Reduces bandwidth usage by 60% on slow networks. Users on 2G get a working experience instead of endless loading. Respects data saver mode to save mobile data costs.