Local Storage & Session Storage
Store data persistently in the browser with Web Storage APIs.
// localStorage - persists across sessions
localStorage.setItem("username", "john_doe");
localStorage.setItem("theme", "dark");
const username = localStorage.getItem("username");
console.log(username); // "john_doe"
localStorage.removeItem("theme");
localStorage.clear(); // Remove all items
// Check if key exists
if (localStorage.getItem("token")) {
console.log("User is logged in");
}
// Storing objects (must stringify)
const user = { name: "John", age: 30, premium: true };
localStorage.setItem("user", JSON.stringify(user));
const storedUser = JSON.parse(localStorage.getItem("user"));
console.log(storedUser.name); // "John"
// sessionStorage - cleared when tab closes
sessionStorage.setItem("tempData", "value");
const temp = sessionStorage.getItem("tempData");
// Iterate over storage
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
console.log(key, localStorage.getItem(key));
}
// Listen for storage changes (from other tabs)
window.addEventListener("storage", (e) => {
console.log("Key:", e.key);
console.log("Old value:", e.oldValue);
console.log("New value:", e.newValue);
console.log("URL:", e.url);
});
// Wrapper for easier use
const storage = {
get(key, defaultValue = null) {
const item = localStorage.getItem(key);
if (!item) return defaultValue;
try {
return JSON.parse(item);
} catch {
return item;
}
},
set(key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
remove(key) {
localStorage.removeItem(key);
}
};
storage.set("settings", { theme: "dark", fontSize: 16 });
console.log(storage.get("settings").theme); // "dark"
LocalStorage typically has a 5-10 MB limit per origin. For larger data, consider IndexedDB. Never store sensitive data (passwords, tokens) in localStorage—it's vulnerable to XSS attacks.
Fetch API
Make HTTP requests to servers and APIs.
// Basic GET request
const response = await fetch("https://api.example.com/users");
const data = await response.json();
console.log(data);
// With error handling
async function fetchData(url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
console.error("Fetch failed:", error);
throw error;
}
}
// POST request
const newUser = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer token123"
},
body: JSON.stringify({
name: "John",
email: "john@example.com"
})
});
// Other HTTP methods
await fetch(url, { method: "PUT", body: JSON.stringify(data) });
await fetch(url, { method: "PATCH", body: JSON.stringify(partial) });
await fetch(url, { method: "DELETE" });
// Response types
const text = await response.text();
const blob = await response.blob();
const buffer = await response.arrayBuffer();
const formData = await response.formData();
// Response properties
console.log(response.ok); // true if 200-299
console.log(response.status); // 200
console.log(response.statusText); // "OK"
console.log(response.headers.get("Content-Type"));
// Abort fetch request
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.then(response => response.json())
.catch(error => {
if (error.name === "AbortError") {
console.log("Request was aborted");
}
});
// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);
// Upload with progress (using XMLHttpRequest)
function uploadWithProgress(url, file, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", (e) => {
if (e.lengthComputable) {
onProgress(e.loaded / e.total * 100);
}
});
xhr.addEventListener("load", () => resolve(xhr.response));
xhr.addEventListener("error", () => reject(new Error("Upload failed")));
xhr.open("POST", url);
xhr.send(file);
});
}
Geolocation API
Get the user's geographic location (with permission).
// Check if supported
if ("geolocation" in navigator) {
console.log("Geolocation is available");
}
// Get current position
navigator.geolocation.getCurrentPosition(
(position) => {
console.log("Latitude:", position.coords.latitude);
console.log("Longitude:", position.coords.longitude);
console.log("Accuracy:", position.coords.accuracy, "meters");
console.log("Altitude:", position.coords.altitude);
console.log("Speed:", position.coords.speed);
console.log("Timestamp:", position.timestamp);
},
(error) => {
switch (error.code) {
case error.PERMISSION_DENIED:
console.log("User denied geolocation");
break;
case error.POSITION_UNAVAILABLE:
console.log("Position unavailable");
break;
case error.TIMEOUT:
console.log("Request timed out");
break;
}
},
{
enableHighAccuracy: true, // Use GPS if available
timeout: 5000, // Max wait time
maximumAge: 0 // Don't use cached position
}
);
// Watch position (continuous updates)
const watchId = navigator.geolocation.watchPosition(
(position) => {
updateMap(position.coords.latitude, position.coords.longitude);
},
(error) => console.error(error),
{ enableHighAccuracy: true }
);
// Stop watching
navigator.geolocation.clearWatch(watchId);
// Promise wrapper
function getLocation() {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject);
});
}
async function showLocation() {
try {
const position = await getLocation();
console.log(position.coords);
} catch (error) {
console.error("Location error:", error);
}
}
Notifications API
Show desktop notifications to users (with permission).
// Check permission status
console.log(Notification.permission);
// "default" | "granted" | "denied"
// Request permission
async function requestNotificationPermission() {
const permission = await Notification.requestPermission();
if (permission === "granted") {
console.log("Notifications enabled!");
} else if (permission === "denied") {
console.log("Notifications blocked");
}
}
// Show notification
function showNotification(title, options = {}) {
if (Notification.permission !== "granted") {
return;
}
const notification = new Notification(title, {
body: "This is the notification body",
icon: "/icon.png",
badge: "/badge.png",
tag: "unique-id", // Replace notifications with same tag
requireInteraction: false, // Auto-dismiss
silent: false, // Play sound
data: { url: "/page" } // Custom data
});
// Event handlers
notification.onclick = (e) => {
window.focus();
window.location.href = e.target.data.url;
notification.close();
};
notification.onclose = () => {
console.log("Notification closed");
};
notification.onerror = (e) => {
console.error("Notification error:", e);
};
// Auto-close after 5 seconds
setTimeout(() => notification.close(), 5000);
return notification;
}
// Practical example
async function notifyNewMessage(message) {
if (Notification.permission === "default") {
await Notification.requestPermission();
}
if (Notification.permission === "granted") {
new Notification(`New message from ${message.sender}`, {
body: message.text,
icon: message.avatar,
tag: "message-" + message.id
});
}
}
Clipboard API
Read from and write to the system clipboard.
// Write text to clipboard
async function copyToClipboard(text) {
try {
await navigator.clipboard.writeText(text);
console.log("Copied to clipboard!");
} catch (error) {
console.error("Failed to copy:", error);
}
}
// Read text from clipboard
async function pasteFromClipboard() {
try {
const text = await navigator.clipboard.readText();
console.log("Pasted:", text);
return text;
} catch (error) {
console.error("Failed to read clipboard:", error);
}
}
// Copy button implementation
const copyButton = document.querySelector(".copy-btn");
const codeBlock = document.querySelector("code");
copyButton.addEventListener("click", async () => {
await copyToClipboard(codeBlock.textContent);
// Visual feedback
copyButton.textContent = "Copied!";
setTimeout(() => {
copyButton.textContent = "Copy";
}, 2000);
});
// Copy rich content (images, HTML)
async function copyImage(imageBlob) {
try {
const item = new ClipboardItem({
"image/png": imageBlob
});
await navigator.clipboard.write([item]);
} catch (error) {
console.error("Failed to copy image:", error);
}
}
// Read clipboard content types
async function readClipboard() {
try {
const items = await navigator.clipboard.read();
for (const item of items) {
console.log("Types:", item.types);
if (item.types.includes("text/plain")) {
const blob = await item.getType("text/plain");
const text = await blob.text();
console.log("Text:", text);
}
if (item.types.includes("image/png")) {
const blob = await item.getType("image/png");
const img = document.createElement("img");
img.src = URL.createObjectURL(blob);
document.body.appendChild(img);
}
}
} catch (error) {
console.error("Clipboard read failed:", error);
}
}
// Fallback for older browsers
function copyTextFallback(text) {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.opacity = "0";
document.body.appendChild(textarea);
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
}
History API
Manipulate browser history for single-page applications.
// Navigate through history
history.back(); // Go back one page
history.forward(); // Go forward one page
history.go(-2); // Go back 2 pages
history.go(1); // Go forward 1 page
// Number of entries in history
console.log(history.length);
// Push new state (adds to history)
history.pushState(
{ page: "about" }, // State object
"", // Title (ignored by most browsers)
"/about" // New URL
);
// Replace current state (doesn't add to history)
history.replaceState(
{ page: "home" },
"",
"/home"
);
// Listen for navigation (back/forward buttons)
window.addEventListener("popstate", (event) => {
console.log("State:", event.state);
if (event.state) {
loadPage(event.state.page);
}
});
// Simple SPA router
class Router {
constructor() {
this.routes = {};
window.addEventListener("popstate", (e) => {
this.navigate(location.pathname, false);
});
}
register(path, handler) {
this.routes[path] = handler;
}
navigate(path, addToHistory = true) {
const handler = this.routes[path];
if (handler) {
if (addToHistory) {
history.pushState({ path }, "", path);
}
handler();
}
}
}
const router = new Router();
router.register("/", () => {
document.getElementById("app").innerHTML = "Home
";
});
router.register("/about", () => {
document.getElementById("app").innerHTML = "About
";
});
// Navigate programmatically
router.navigate("/about");
// Handle link clicks
document.addEventListener("click", (e) => {
if (e.target.matches("[data-link]")) {
e.preventDefault();
router.navigate(e.target.getAttribute("href"));
}
});
Other Useful APIs
// ===== Intersection Observer (visibility detection) =====
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
observer.unobserve(entry.target); // Stop observing
}
});
}, {
threshold: 0.5 // Trigger when 50% visible
});
document.querySelectorAll(".animate-on-scroll").forEach(el => {
observer.observe(el);
});
// ===== Performance API =====
// Measure execution time
performance.mark("start");
// ... some code ...
performance.mark("end");
performance.measure("myOperation", "start", "end");
const measures = performance.getEntriesByName("myOperation");
console.log(`Took ${measures[0].duration}ms`);
// Navigation timing
const timing = performance.getEntriesByType("navigation")[0];
console.log(`Page load: ${timing.loadEventEnd - timing.startTime}ms`);
// ===== Screen API =====
console.log(`Screen: ${screen.width}x${screen.height}`);
console.log(`Available: ${screen.availWidth}x${screen.availHeight}`);
console.log(`Color depth: ${screen.colorDepth}`);
// Fullscreen
async function toggleFullscreen(element) {
if (!document.fullscreenElement) {
await element.requestFullscreen();
} else {
await document.exitFullscreen();
}
}
// ===== Vibration API (mobile) =====
navigator.vibrate(200); // Vibrate 200ms
navigator.vibrate([100, 50, 100]); // Pattern: vibrate-pause-vibrate
navigator.vibrate(0); // Stop vibration
// ===== Share API (mobile) =====
async function shareContent() {
if (navigator.share) {
try {
await navigator.share({
title: "Check this out!",
text: "An interesting article",
url: "https://example.com"
});
} catch (error) {
console.log("Share cancelled");
}
}
}
// ===== MediaDevices API =====
async function getCamera() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
const video = document.querySelector("video");
video.srcObject = stream;
video.play();
} catch (error) {
console.error("Camera access denied:", error);
}
}
// ===== Web Speech API =====
// Text to speech
const utterance = new SpeechSynthesisUtterance("Hello, world!");
speechSynthesis.speak(utterance);
// Speech recognition
const recognition = new webkitSpeechRecognition();
recognition.onresult = (event) => {
console.log("You said:", event.results[0][0].transcript);
};
recognition.start();
Web Workers
Web Workers allow running JavaScript in background threads, keeping the main thread responsive for UI interactions. They're ideal for heavy computations, data processing, or any CPU-intensive tasks.
Basic Worker Setup
// Create a new worker
const worker = new Worker('worker.js');
// Send data to worker
worker.postMessage({
type: 'PROCESS_DATA',
data: largeArray
});
// Receive results from worker
worker.onmessage = (event) => {
console.log('Processed result:', event.data);
updateUI(event.data);
};
// Handle errors
worker.onerror = (error) => {
console.error('Worker error:', error.message);
console.error('At line:', error.lineno);
};
// Terminate worker when done
function cleanup() {
worker.terminate();
}
// worker.js - runs in separate thread
self.onmessage = (event) => {
const { type, data } = event.data;
switch (type) {
case 'PROCESS_DATA':
const result = processLargeData(data);
self.postMessage(result);
break;
case 'STOP':
self.close(); // Worker terminates itself
break;
}
};
function processLargeData(array) {
// Heavy computation here won't block the UI
return array
.map(item => complexCalculation(item))
.filter(item => item.valid)
.reduce((acc, item) => acc + item.value, 0);
}
// Workers can import scripts
importScripts('utils.js', 'lodash.min.js');
Inline Workers with Blob
// Create worker without separate file
function createInlineWorker(fn) {
const blob = new Blob(
[`self.onmessage = ${fn.toString()}`],
{ type: 'application/javascript' }
);
return new Worker(URL.createObjectURL(blob));
}
// Usage
const worker = createInlineWorker((e) => {
const result = e.data.numbers.reduce((a, b) => a + b, 0);
self.postMessage({ sum: result });
});
worker.postMessage({ numbers: [1, 2, 3, 4, 5] });
worker.onmessage = (e) => console.log('Sum:', e.sum);
// Transferable objects (zero-copy transfer)
const buffer = new ArrayBuffer(1024 * 1024); // 1MB
worker.postMessage(buffer, [buffer]); // Transfer, not copy
console.log(buffer.byteLength); // 0 - ownership transferred
Service Workers
Service Workers act as proxy servers between your app and the network. They enable offline functionality, push notifications, and background sync.
Registration and Lifecycle
// Register service worker (in main app)
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
try {
const registration = await navigator.serviceWorker.register('/sw.js', {
scope: '/' // Control pages at this path
});
console.log('SW registered:', registration.scope);
// Check for updates
registration.addEventListener('updatefound', () => {
const newWorker = registration.installing;
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// New version available
showUpdateNotification();
}
}
});
});
} catch (error) {
console.error('SW registration failed:', error);
}
});
}
// Communicate with service worker
navigator.serviceWorker.controller?.postMessage({
type: 'SKIP_WAITING'
});
Caching Strategies
// sw.js - Service Worker file
const CACHE_NAME = 'app-cache-v1';
const ASSETS = [
'/',
'/index.html',
'/css/style.css',
'/js/app.js',
'/offline.html'
];
// Install: cache static assets
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(ASSETS))
.then(() => self.skipWaiting())
);
});
// Activate: clean up old caches
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(keys => {
return Promise.all(
keys.filter(key => key !== CACHE_NAME)
.map(key => caches.delete(key))
);
}).then(() => self.clients.claim())
);
});
// Fetch: network-first with cache fallback
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request)
.then(response => {
// Clone and cache successful responses
const clone = response.clone();
caches.open(CACHE_NAME)
.then(cache => cache.put(event.request, clone));
return response;
})
.catch(() => {
// Network failed, try cache
return caches.match(event.request)
.then(cached => cached || caches.match('/offline.html'));
})
);
});
IndexedDB
IndexedDB is a low-level API for storing large amounts of structured data. Unlike localStorage, it can store files, blobs, and handles complex queries with indexes.
Database Setup
// Promise-based IndexedDB wrapper
class Database {
constructor(name, version) {
this.name = name;
this.version = version;
this.db = null;
}
async open(upgradeCallback) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.name, this.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve(this);
};
request.onupgradeneeded = (event) => {
this.db = event.target.result;
upgradeCallback(this.db, event.oldVersion);
};
});
}
async add(storeName, data) {
return this.transaction(storeName, 'readwrite', store => {
return store.add(data);
});
}
async put(storeName, data) {
return this.transaction(storeName, 'readwrite', store => {
return store.put(data);
});
}
async get(storeName, key) {
return this.transaction(storeName, 'readonly', store => {
return store.get(key);
});
}
async getAll(storeName, query = null) {
return this.transaction(storeName, 'readonly', store => {
return store.getAll(query);
});
}
async delete(storeName, key) {
return this.transaction(storeName, 'readwrite', store => {
return store.delete(key);
});
}
transaction(storeName, mode, callback) {
return new Promise((resolve, reject) => {
const tx = this.db.transaction(storeName, mode);
const store = tx.objectStore(storeName);
const request = callback(store);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
}
// Usage
const db = new Database('MyApp', 1);
await db.open((database, oldVersion) => {
if (oldVersion < 1) {
const store = database.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
store.createIndex('email', 'email', { unique: true });
store.createIndex('createdAt', 'createdAt');
}
});
await db.add('users', { name: 'Alice', email: 'alice@example.com', createdAt: Date.now() });
const users = await db.getAll('users');
Intersection Observer
Intersection Observer efficiently detects when elements enter or leave the viewport. It's perfect for lazy loading, infinite scroll, and scroll-triggered animations.
Lazy Loading Images
// Create observer for lazy loading
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
// Load the actual image
img.src = img.dataset.src;
if (img.dataset.srcset) {
img.srcset = img.dataset.srcset;
}
img.classList.add('loaded');
observer.unobserve(img); // Stop observing once loaded
}
});
}, {
root: null, // Viewport
rootMargin: '50px', // Load slightly before visible
threshold: 0 // Trigger as soon as 1px is visible
});
// Observe all lazy images
document.querySelectorAll('img[data-src]').forEach(img => {
imageObserver.observe(img);
});
// HTML:
// <img data-src="large-image.jpg" data-srcset="small.jpg 400w, large.jpg 800w" alt="...">
Infinite Scroll
// Infinite scroll implementation
class InfiniteScroll {
constructor(container, loadMore) {
this.container = container;
this.loadMore = loadMore;
this.loading = false;
this.page = 1;
// Create sentinel element
this.sentinel = document.createElement('div');
this.sentinel.className = 'scroll-sentinel';
container.appendChild(this.sentinel);
// Observer for the sentinel
this.observer = new IntersectionObserver(
this.handleIntersect.bind(this),
{ rootMargin: '100px' }
);
this.observer.observe(this.sentinel);
}
async handleIntersect(entries) {
const entry = entries[0];
if (entry.isIntersecting && !this.loading) {
this.loading = true;
this.showLoader();
try {
const items = await this.loadMore(this.page);
if (items.length > 0) {
this.appendItems(items);
this.page++;
} else {
this.observer.disconnect();
this.showEndMessage();
}
} catch (error) {
console.error('Load failed:', error);
} finally {
this.loading = false;
this.hideLoader();
}
}
}
appendItems(items) {
const fragment = document.createDocumentFragment();
items.forEach(item => {
fragment.appendChild(this.createItemElement(item));
});
this.container.insertBefore(fragment, this.sentinel);
}
createItemElement(item) {
const div = document.createElement('div');
div.className = 'item';
div.textContent = item.title;
return div;
}
showLoader() { /* ... */ }
hideLoader() { /* ... */ }
showEndMessage() { /* ... */ }
}
// Usage
new InfiniteScroll(
document.getElementById('feed'),
async (page) => {
const res = await fetch(`/api/items?page=${page}`);
return res.json();
}
);
Scroll Animations
// Animate elements when they enter viewport
const animationObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const el = entry.target;
const animation = el.dataset.animation || 'fade-in';
const delay = el.dataset.delay || '0';
el.style.animationDelay = `${delay}ms`;
el.classList.add('animate', animation);
// Optional: unobserve after animation
animationObserver.unobserve(el);
}
});
}, {
threshold: 0.2 // 20% visible before triggering
});
// Observe animated elements
document.querySelectorAll('[data-animation]').forEach(el => {
animationObserver.observe(el);
});
// Multiple thresholds for progress tracking
const progressObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
// intersectionRatio tells you how much is visible
const visibility = Math.round(entry.intersectionRatio * 100);
entry.target.style.setProperty('--visibility', visibility);
});
}, {
threshold: Array.from({ length: 101 }, (_, i) => i / 100) // 0, 0.01, 0.02, ... 1
});
- Feature detection: Always check if an API exists before using it
- Permissions: Request permissions at the right time with context
- Error handling: APIs can fail for many reasons - always handle errors
- Fallbacks: Provide graceful degradation for unsupported browsers
Summary
Web Storage
localStorage, sessionStorage for data persistence
Fetch API
HTTP requests, async/await, AbortController
Geolocation
Get user location with permission
Notifications
Desktop notifications with permission
Clipboard
Read/write system clipboard
History
SPA routing, pushState, popstate