# JavaScript Network Information API Complete Guide
Master network status detection: connection type identification, bandwidth estimation, adaptive loading, and offline handling strategies
The Network Information API allows web pages to access device network connection information. This article covers its usage and practical applications.
Basic Concepts
Detecting Support
// Check API support
if ('connection' in navigator) {
console.log('Network Information API is supported');
const connection = navigator.connection;
console.log(connection);
} else {
console.log('Network Information API is not supported');
}
// Compatibility handling
const connection = navigator.connection ||
navigator.mozConnection ||
navigator.webkitConnection;
Connection Properties
const connection = navigator.connection;
if (connection) {
// Effective connection type: slow-2g, 2g, 3g, 4g
console.log('Effective type:', connection.effectiveType);
// Actual connection type: bluetooth, cellular, ethernet, wifi, wimax, other, unknown
console.log('Connection type:', connection.type);
// Downlink bandwidth estimate (Mbps)
console.log('Downlink:', connection.downlink, 'Mbps');
// Round-trip time estimate (milliseconds)
console.log('RTT:', connection.rtt, 'ms');
// Whether data saver mode is enabled
console.log('Save data:', connection.saveData);
// Maximum downlink speed (Mbps)
console.log('Max downlink:', connection.downlinkMax, 'Mbps');
}
Listening to Changes
const connection = navigator.connection;
if (connection) {
connection.addEventListener('change', () => {
console.log('Network status changed:');
console.log(' Type:', connection.effectiveType);
console.log(' Bandwidth:', connection.downlink, 'Mbps');
console.log(' RTT:', connection.rtt, 'ms');
});
}
Network Manager
Wrapper Class
class NetworkManager {
constructor() {
this.connection = navigator.connection ||
navigator.mozConnection ||
navigator.webkitConnection;
this.listeners = new Set();
this.isOnline = navigator.onLine;
this.init();
}
init() {
// Listen for network status changes
if (this.connection) {
this.connection.addEventListener('change', () => {
this.notify('connectionChange', this.getStatus());
});
}
// Listen for online/offline status
window.addEventListener('online', () => {
this.isOnline = true;
this.notify('online', this.getStatus());
});
window.addEventListener('offline', () => {
this.isOnline = false;
this.notify('offline', this.getStatus());
});
}
get isSupported() {
return !!this.connection;
}
getStatus() {
if (!this.connection) {
return {
isOnline: this.isOnline,
effectiveType: 'unknown',
type: 'unknown',
downlink: null,
rtt: null,
saveData: false
};
}
return {
isOnline: this.isOnline,
effectiveType: this.connection.effectiveType,
type: this.connection.type,
downlink: this.connection.downlink,
rtt: this.connection.rtt,
saveData: this.connection.saveData,
downlinkMax: this.connection.downlinkMax
};
}
// Check if connection is fast
isFastConnection() {
if (!this.connection) return true; // Assume fast by default
const { effectiveType, downlink } = this.connection;
return effectiveType === '4g' && downlink >= 5;
}
// Check if connection is slow
isSlowConnection() {
if (!this.connection) return false;
const { effectiveType } = this.connection;
return effectiveType === 'slow-2g' || effectiveType === '2g';
}
// Check if data should be saved
shouldSaveData() {
if (!this.connection) return false;
return this.connection.saveData || this.isSlowConnection();
}
onChange(callback) {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
notify(event, data) {
this.listeners.forEach(callback => callback(event, data));
}
}
// Usage
const network = new NetworkManager();
console.log('Current network status:', network.getStatus());
network.onChange((event, status) => {
console.log('Network event:', event);
console.log('Status:', status);
});
Connection Quality Assessment
class ConnectionQuality {
constructor() {
this.network = new NetworkManager();
}
// Get quality level: excellent, good, fair, poor
getQuality() {
const status = this.network.getStatus();
if (!status.isOnline) return 'offline';
const { effectiveType, downlink, rtt } = status;
// Evaluate based on multiple factors
if (effectiveType === '4g' && downlink >= 10 && rtt < 50) {
return 'excellent';
}
if (effectiveType === '4g' && downlink >= 5) {
return 'good';
}
if (effectiveType === '3g' || (effectiveType === '4g' && downlink < 5)) {
return 'fair';
}
return 'poor';
}
// Get recommended media quality
getRecommendedMediaQuality() {
const quality = this.getQuality();
switch (quality) {
case 'excellent':
return { video: '1080p', image: 'high', prefetch: true };
case 'good':
return { video: '720p', image: 'medium', prefetch: true };
case 'fair':
return { video: '480p', image: 'low', prefetch: false };
case 'poor':
return { video: '360p', image: 'thumbnail', prefetch: false };
default:
return { video: 'none', image: 'placeholder', prefetch: false };
}
}
// Get recommended request strategy
getRequestStrategy() {
const quality = this.getQuality();
switch (quality) {
case 'excellent':
return {
timeout: 5000,
retries: 2,
batchSize: 10,
concurrent: 6
};
case 'good':
return {
timeout: 10000,
retries: 3,
batchSize: 5,
concurrent: 4
};
case 'fair':
return {
timeout: 15000,
retries: 4,
batchSize: 3,
concurrent: 2
};
case 'poor':
return {
timeout: 30000,
retries: 5,
batchSize: 1,
concurrent: 1
};
default:
return {
timeout: 60000,
retries: 10,
batchSize: 1,
concurrent: 1
};
}
}
}
// Usage
const connectionQuality = new ConnectionQuality();
console.log('Connection quality:', connectionQuality.getQuality());
console.log('Recommended media quality:', connectionQuality.getRecommendedMediaQuality());
console.log('Request strategy:', connectionQuality.getRequestStrategy());
Practical Applications
Adaptive Image Loading
class AdaptiveImageLoader {
constructor() {
this.network = new NetworkManager();
}
// Get optimal image URL
getOptimalImageUrl(baseUrl, sizes = {}) {
const status = this.network.getStatus();
// Default size configuration
const defaultSizes = {
high: 'large',
medium: 'medium',
low: 'small',
thumbnail: 'thumb'
};
const sizeSuffixes = { ...defaultSizes, ...sizes };
// Choose based on network conditions
if (!status.isOnline) {
return null;
}
if (this.network.shouldSaveData()) {
return this.buildUrl(baseUrl, sizeSuffixes.thumbnail);
}
switch (status.effectiveType) {
case '4g':
return this.buildUrl(baseUrl, sizeSuffixes.high);
case '3g':
return this.buildUrl(baseUrl, sizeSuffixes.medium);
case '2g':
return this.buildUrl(baseUrl, sizeSuffixes.low);
default:
return this.buildUrl(baseUrl, sizeSuffixes.thumbnail);
}
}
buildUrl(baseUrl, size) {
// Assume URL format: /images/photo.jpg -> /images/photo-large.jpg
const ext = baseUrl.substring(baseUrl.lastIndexOf('.'));
const name = baseUrl.substring(0, baseUrl.lastIndexOf('.'));
return `${name}-${size}${ext}`;
}
// Lazy load image
async loadImage(img, baseUrl) {
const optimalUrl = this.getOptimalImageUrl(baseUrl);
if (!optimalUrl) {
img.src = '/images/placeholder.jpg';
return;
}
return new Promise((resolve, reject) => {
const tempImg = new Image();
tempImg.onload = () => {
img.src = optimalUrl;
resolve(optimalUrl);
};
tempImg.onerror = () => {
img.src = '/images/placeholder.jpg';
reject(new Error('Failed to load image'));
};
tempImg.src = optimalUrl;
});
}
// Process all images on page
async processImages(selector = 'img[data-src]') {
const images = document.querySelectorAll(selector);
for (const img of images) {
const baseUrl = img.dataset.src;
if (baseUrl) {
await this.loadImage(img, baseUrl);
}
}
}
}
// Usage
const imageLoader = new AdaptiveImageLoader();
// Process all lazy-load images
imageLoader.processImages();
// Load single image
const img = document.getElementById('hero-image');
imageLoader.loadImage(img, '/images/hero.jpg');
Adaptive Video Quality
class AdaptiveVideoPlayer {
constructor(videoElement) {
this.video = videoElement;
this.network = new NetworkManager();
this.currentQuality = null;
this.qualities = {
'1080p': { width: 1920, bitrate: 5000 },
'720p': { width: 1280, bitrate: 2500 },
'480p': { width: 854, bitrate: 1000 },
'360p': { width: 640, bitrate: 500 },
'240p': { width: 426, bitrate: 250 }
};
this.init();
}
init() {
this.network.onChange((event, status) => {
if (event === 'connectionChange') {
this.adjustQuality();
}
});
// Initial quality adjustment
this.adjustQuality();
}
adjustQuality() {
const status = this.network.getStatus();
const recommendedQuality = this.getRecommendedQuality(status);
if (recommendedQuality !== this.currentQuality) {
this.setQuality(recommendedQuality);
}
}
getRecommendedQuality(status) {
if (!status.isOnline) return '240p';
const { downlink, effectiveType, saveData } = status;
if (saveData) return '360p';
// Choose based on bandwidth
if (downlink >= 10 && effectiveType === '4g') {
return '1080p';
} else if (downlink >= 5) {
return '720p';
} else if (downlink >= 2) {
return '480p';
} else if (downlink >= 1) {
return '360p';
} else {
return '240p';
}
}
setQuality(quality) {
const currentTime = this.video.currentTime;
const wasPlaying = !this.video.paused;
// Update video source
const qualityConfig = this.qualities[quality];
const newSrc = this.buildVideoUrl(quality);
this.video.src = newSrc;
this.video.currentTime = currentTime;
this.currentQuality = quality;
if (wasPlaying) {
this.video.play();
}
this.onQualityChange(quality);
}
buildVideoUrl(quality) {
const basePath = this.video.dataset.basePath || '';
return `${basePath}/video-${quality}.mp4`;
}
onQualityChange(quality) {
console.log('Video quality changed to:', quality);
// Update UI
const indicator = document.getElementById('quality-indicator');
if (indicator) {
indicator.textContent = quality;
}
}
// Manually set quality
forceQuality(quality) {
if (this.qualities[quality]) {
this.setQuality(quality);
}
}
// Get current quality
getCurrentQuality() {
return this.currentQuality;
}
}
// Usage
const video = document.getElementById('main-video');
const player = new AdaptiveVideoPlayer(video);
Smart Prefetching
class SmartPrefetcher {
constructor() {
this.network = new NetworkManager();
this.prefetchedUrls = new Set();
}
// Check if prefetching should occur
shouldPrefetch() {
const status = this.network.getStatus();
if (!status.isOnline) return false;
if (status.saveData) return false;
if (this.network.isSlowConnection()) return false;
return true;
}
// Prefetch resource
async prefetch(url) {
if (!this.shouldPrefetch()) return false;
if (this.prefetchedUrls.has(url)) return true;
try {
const link = document.createElement('link');
link.rel = 'prefetch';
link.href = url;
document.head.appendChild(link);
this.prefetchedUrls.add(url);
return true;
} catch (error) {
console.error('Prefetch failed:', url, error);
return false;
}
}
// Preconnect to origin
preconnect(origin) {
if (!this.shouldPrefetch()) return;
const link = document.createElement('link');
link.rel = 'preconnect';
link.href = origin;
document.head.appendChild(link);
}
// Prefetch multiple resources
async prefetchMultiple(urls) {
if (!this.shouldPrefetch()) return [];
const status = this.network.getStatus();
// Limit concurrency based on network
const concurrentLimit = status.effectiveType === '4g' ? 5 : 2;
const results = [];
for (let i = 0; i < urls.length; i += concurrentLimit) {
const batch = urls.slice(i, i + concurrentLimit);
const batchResults = await Promise.all(
batch.map(url => this.prefetch(url))
);
results.push(...batchResults);
}
return results;
}
// Smart prefetch next page
prefetchNextPage(links) {
if (!this.shouldPrefetch()) return;
// Decide prefetch count based on network quality
const status = this.network.getStatus();
let count = 1;
if (status.effectiveType === '4g' && status.downlink >= 10) {
count = 3;
} else if (status.effectiveType === '4g') {
count = 2;
}
const urlsToFetch = links.slice(0, count);
this.prefetchMultiple(urlsToFetch);
}
}
// Usage
const prefetcher = new SmartPrefetcher();
// Prefetch resource
prefetcher.prefetch('/api/data.json');
// Prefetch multiple resources
prefetcher.prefetchMultiple([
'/images/hero.jpg',
'/js/page-2.js',
'/css/page-2.css'
]);
// Prefetch on hover
document.querySelectorAll('a[data-prefetch]').forEach(link => {
link.addEventListener('mouseenter', () => {
prefetcher.prefetch(link.href);
});
});
Offline-Aware Application
class OfflineAwareApp {
constructor() {
this.network = new NetworkManager();
this.pendingActions = [];
this.isOffline = !navigator.onLine;
this.init();
}
init() {
this.network.onChange((event, status) => {
if (event === 'online') {
this.handleOnline();
} else if (event === 'offline') {
this.handleOffline();
}
});
}
handleOnline() {
this.isOffline = false;
console.log('Network restored, processing pending actions...');
this.showNotification('Network restored', 'success');
this.syncPendingActions();
}
handleOffline() {
this.isOffline = true;
console.log('Network disconnected');
this.showNotification('Network disconnected, actions will sync when restored', 'warning');
}
// Perform network-required action
async performAction(action, data) {
if (this.isOffline) {
// Queue action when offline
this.queueAction(action, data);
return { queued: true, message: 'Action saved, will execute when online' };
}
try {
return await this.executeAction(action, data);
} catch (error) {
if (this.isNetworkError(error)) {
this.queueAction(action, data);
return { queued: true, message: 'Network error, action saved' };
}
throw error;
}
}
queueAction(action, data) {
const queuedAction = {
id: Date.now(),
action,
data,
timestamp: new Date().toISOString()
};
this.pendingActions.push(queuedAction);
this.savePendingActions();
return queuedAction;
}
async syncPendingActions() {
const actions = [...this.pendingActions];
this.pendingActions = [];
for (const item of actions) {
try {
await this.executeAction(item.action, item.data);
console.log('Sync successful:', item.action);
} catch (error) {
console.error('Sync failed:', item.action, error);
this.pendingActions.push(item);
}
}
this.savePendingActions();
}
async executeAction(action, data) {
// Actual API call
const response = await fetch(`/api/${action}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
return response.json();
}
isNetworkError(error) {
return error.name === 'TypeError' && error.message === 'Failed to fetch';
}
savePendingActions() {
localStorage.setItem('pendingActions', JSON.stringify(this.pendingActions));
}
loadPendingActions() {
try {
const saved = localStorage.getItem('pendingActions');
this.pendingActions = saved ? JSON.parse(saved) : [];
} catch {
this.pendingActions = [];
}
}
showNotification(message, type) {
// Display notification UI
console.log(`[${type}] ${message}`);
}
getPendingCount() {
return this.pendingActions.length;
}
}
// Usage
const app = new OfflineAwareApp();
app.loadPendingActions();
// Perform action
document.getElementById('save-btn').addEventListener('click', async () => {
const result = await app.performAction('saveData', { content: 'test' });
console.log('Result:', result);
});
Best Practices Summary
Network Information API Best Practices:
┌─────────────────────────────────────────────────────┐
│ │
│ Adaptive Loading │
│ ├── Adjust media quality based on connection │
│ ├── Use placeholders on slow networks │
│ ├── Respect user's data saver preferences │
│ └── Dynamically adjust prefetch strategy │
│ │
│ Offline Handling │
│ ├── Listen for online/offline events │
│ ├── Cache data with local storage │
│ ├── Implement action queue and sync │
│ └── Provide clear status feedback │
│ │
│ Performance Optimization │
│ ├── Avoid frequent network status checks │
│ ├── Use event listeners instead of polling │
│ ├── Set appropriate timeout and retry strategies │
│ └── Consider API compatibility │
│ │
└─────────────────────────────────────────────────────┘
| Connection Type | Typical Bandwidth | Recommended Strategy |
|---|---|---|
| 4g | >10 Mbps | High quality media, aggressive prefetch |
| 3g | 1-10 Mbps | Medium quality, moderate prefetch |
| 2g | 50-250 Kbps | Low quality, disable prefetch |
| slow-2g | <50 Kbps | Minimum quality, text-first |
Master the Network Information API to provide optimal network experience for users.