Returns element with specified ID. Returns null if not found. Most performant selector.
All Browsers
getElementsByClassName
element.getElementsByClassName(names)
Returns live HTMLCollection of elements with specified class names (space-separated). Updates automatically when DOM changes.
All Browsers
getElementsByTagName
element.getElementsByTagName(name)
Returns live HTMLCollection of elements with specified tag name. Use "*" for all elements.
All Browsers
querySelector
element.querySelector(selector)
Returns first element matching CSS selector. Returns null if no match. Static snapshot.
All Browsers
querySelectorAll
element.querySelectorAll(selector)
Returns static NodeList of all matching elements. Use forEach() to iterate. Non-live collection.
All Browsers
getElementsByName
document.getElementsByName(name)
Returns live NodeList of elements with specified name attribute. Primarily for form elements.
All Browsers
closest
element.closest(selector)
Traverses element and ancestors, returns first matching element. Returns null if no match.
Modern Browsers
matches
element.matches(selector)
Returns boolean indicating if element matches CSS selector. Useful for event delegation.
Modern Browsers
Example: Selection methods comparison
// Get by ID - fastestconst header = document.getElementById("header");// Query selector - flexible CSS selectorsconst firstButton = document.querySelector(".btn-primary");const allButtons = document.querySelectorAll("button.active");// Live collections - auto-updateconst items = document.getElementsByClassName("item");console.log(items.length); // 5document.body.appendChild(newItem); // Adds .item elementconsole.log(items.length); // 6 - auto-updated// Closest for ancestor lookupconst card = button.closest(".card");// Matches for filteringif (element.matches("[data-active='true']")) { // Element has attribute}
Note:querySelector returns static NodeList while getElementsByClassName returns live HTMLCollection. Live collections automatically update when DOM changes. Use querySelector for static snapshots to avoid performance issues.
1.2 DOM Node Creation and Manipulation
Method
Syntax
Description
Use Case
createElement
document.createElement(tagName)
Creates new element node with specified tag name. Element not in DOM until appended.
Dynamic content generation
createTextNode
document.createTextNode(data)
Creates text node with specified string. Automatically escapes HTML entities.
Safe text insertion
createDocumentFragment
document.createDocumentFragment()
Creates lightweight document fragment. Batch DOM operations for performance.
Bulk insertions
appendChild
parent.appendChild(child)
Appends node as last child. Moves node if already in DOM. Returns appended node.
Add to end of parent
insertBefore
parent.insertBefore(new, ref)
Inserts node before reference node. Use null as ref to append at end.
Insert at specific position
replaceChild
parent.replaceChild(new, old)
Replaces old child with new node. Returns replaced node.
Swap elements
removeChild
parent.removeChild(child)
Removes child node from parent. Returns removed node. Throws error if not child.
Remove specific child
cloneNode
node.cloneNode(deep)
Creates copy of node. deep=true clones descendants. IDs are duplicated.
Duplicate elements
append NEW
parent.append(...nodes)
Appends multiple nodes or strings. Accepts DOMString. No return value.
Modern bulk append
prepend NEW
parent.prepend(...nodes)
Inserts nodes before first child. Accepts multiple arguments.
Add to beginning
before NEW
element.before(...nodes)
Inserts nodes before element in parent's child list.
Insert before sibling
after NEW
element.after(...nodes)
Inserts nodes after element in parent's child list.
Insert after sibling
replaceWith NEW
element.replaceWith(...nodes)
Replaces element with nodes/strings. More convenient than replaceChild.
Modern replacement
remove NEW
element.remove()
Removes element from DOM tree. No parent reference needed.
Self-removal
Example: Creating and inserting elements
// Traditional approachconst div = document.createElement("div");div.textContent = "Hello";document.body.appendChild(div);// Modern approach - multiple nodesconst container = document.querySelector("#container");container.append("Text", document.createElement("span"), "More text");// Document fragment for performanceconst fragment = document.createDocumentFragment();for (let i = 0; i < 1000; i++) { const item = document.createElement("li"); item.textContent = `Item ${i}`; fragment.appendChild(item);}list.appendChild(fragment); // Single reflow// Clone with deep copyconst original = document.querySelector(".template");const copy = original.cloneNode(true);copy.id = "new-id"; // Change duplicate IDdocument.body.appendChild(copy);// Modern insertion methodselement.before("Before"); // Insert beforeelement.after("After"); // Insert afterelement.replaceWith("Replacement"); // Replaceelement.remove(); // Remove from DOM
Warning: Using appendChild on an existing DOM node will move it, not copy it. Use cloneNode() first to duplicate. Document fragments are destroyed after insertion - create new one for each batch operation.
1.3 Element Attributes and Properties
Method/Property
Syntax
Description
Use Case
getAttribute
element.getAttribute(name)
Returns attribute value as string. Returns null if attribute doesn't exist.
Read custom attributes
setAttribute
element.setAttribute(name, value)
Sets attribute to specified value. Creates attribute if doesn't exist. Converts value to string.
Set any attribute
removeAttribute
element.removeAttribute(name)
Removes attribute completely. No error if attribute doesn't exist.
Remove attributes
hasAttribute
element.hasAttribute(name)
Returns boolean indicating if element has specified attribute.
Check attribute existence
toggleAttribute NEW
element.toggleAttribute(name, force)
Toggles boolean attribute. Optional force parameter adds/removes explicitly.
Toggle boolean attributes
attributes
element.attributes
Returns live NamedNodeMap of all attributes. Use .name and .value properties.
Iterate all attributes
dataset
element.dataset
DOMStringMap of data-* attributes. Converts kebab-case to camelCase. Read/write custom data.
Custom data attributes
id
element.id
Direct property access to element ID. Faster than getAttribute. Reflects in HTML.
ID manipulation
className
element.className
Gets/sets class attribute as string. Space-separated for multiple classes.
Bulk class replacement
innerHTML
element.innerHTML
Gets/sets HTML content as string. Parses HTML. XSS risk with user input.
Set HTML content
outerHTML
element.outerHTML
Gets/sets element and its content. Setting replaces entire element.
Replace element with HTML
textContent
element.textContent
Gets/sets text content. Strips HTML tags. Safe from XSS. Faster than innerText.
Gets/sets form input value. For inputs, textareas, selects. Live property.
Form field values
checked
input.checked
Boolean for checkbox/radio state. Use property, not attribute for current state.
Checkbox/radio state
Example: Attribute manipulation
// Get/set attributesconst link = document.querySelector("a");link.getAttribute("href"); // Getlink.setAttribute("href", "/new"); // Setlink.removeAttribute("target"); // Remove// Has attribute checkif (button.hasAttribute("disabled")) { console.log("Button is disabled");}// Toggle attributebutton.toggleAttribute("disabled");button.toggleAttribute("hidden", true); // Force add// Data attributeselement.setAttribute("data-user-id", "123");console.log(element.dataset.userId); // "123"element.dataset.userName = "John"; // Creates data-user-name
Example: Content manipulation
// innerHTML - parses HTML (XSS risk)div.innerHTML = "<strong>Bold</strong>";// textContent - safe, no parsingdiv.textContent = "<script>alert('safe')</script>";// Displays as literal text// innerText vs textContentdiv.innerHTML = "<span style='display:none'>Hidden</span>Text";console.log(div.textContent); // "HiddenText"console.log(div.innerText); // "Text" (respects CSS)// Form valuesinput.value = "New value";checkbox.checked = true;select.value = "option2";
Warning: Never use innerHTML with unsanitized user input - creates XSS vulnerability. Use textContent or createTextNode() for user-generated content. Properties like checked and value reflect current state, while attributes show initial HTML values.
1.4 CSS Classes and Styling Manipulation
Property/Method
Syntax
Description
Use Case
classList.add
element.classList.add(...tokens)
Adds one or more class names. No duplicates. Does nothing if class exists.
Add classes
classList.remove
element.classList.remove(...tokens)
Removes one or more class names. No error if class doesn't exist.
Remove classes
classList.toggle
element.classList.toggle(token, force)
Toggles class. Returns boolean indicating if class is present after operation. Optional force parameter.
Toggle states
classList.contains
element.classList.contains(token)
Returns boolean indicating if class exists. Case-sensitive check.
Check class existence
classList.replace
element.classList.replace(old, new)
Replaces old class with new class. Returns boolean indicating if replacement occurred.
Swap classes
classList.item
element.classList.item(index)
Returns class name at specified index. Returns null if out of bounds.
Access by index
classList.length
element.classList.length
Returns number of classes. Read-only property.
Count classes
style
element.style.property
Inline styles as CSSStyleDeclaration. Use camelCase for CSS properties. High specificity.
Inline styles
style.cssText
element.style.cssText
Gets/sets all inline styles as string. Replaces existing inline styles when set.
Returns live CSSStyleDeclaration of computed styles. Includes inherited/default values. Read-only.
Read final styles
attributeStyleMap NEW
element.attributeStyleMap
CSS Typed OM for type-safe style manipulation. Returns StylePropertyMap.
Modern typed styles
Example: Class manipulation with classList
// Add single or multiple classeselement.classList.add("active");element.classList.add("highlight", "bold", "large");// Remove classeselement.classList.remove("inactive");// Toggle classconst isActive = element.classList.toggle("active");console.log(isActive); // true if added, false if removed// Force toggleelement.classList.toggle("active", true); // Always addelement.classList.toggle("active", false); // Always remove// Check if class existsif (element.classList.contains("active")) { console.log("Element is active");}// Replace classelement.classList.replace("old-theme", "new-theme");// Iterate classesfor (let className of element.classList) { console.log(className);}
Example: Style manipulation
// Direct style property (camelCase)element.style.backgroundColor = "#ff0000";element.style.fontSize = "16px";element.style.marginTop = "20px";// Set multiple styles at onceelement.style.cssText = "color: blue; font-size: 14px; padding: 10px;";// Set with priorityelement.style.setProperty("color", "red", "important");// Remove inline styleelement.style.removeProperty("background-color");// Get computed styles (read-only)const computed = getComputedStyle(element);console.log(computed.color); // "rgb(255, 0, 0)"console.log(computed.fontSize); // "16px"// Get pseudo-element stylesconst beforeStyles = getComputedStyle(element, "::before");console.log(beforeStyles.content);// CSS Typed OM (modern)element.attributeStyleMap.set("opacity", 0.5);const opacity = element.attributeStyleMap.get("opacity");
Note: Prefer classList over className for class manipulation - it's more reliable and prevents accidentally removing other classes. Use CSS classes for styling instead of inline styles when possible for better separation of concerns and maintainability.
1.5 DOM Traversal and Navigation Methods
Property
Description
Returns
Notes
parentNode
Returns parent node of element
Node or null
Includes non-element nodes
parentElement
Returns parent element of element
Element or null
Element nodes only
childNodes
Live NodeList of all child nodes
NodeList
Includes text, comment nodes
children
Live HTMLCollection of child elements
HTMLCollection
Element nodes only
firstChild
First child node
Node or null
May be text/comment node
lastChild
Last child node
Node or null
May be text/comment node
firstElementChild
First child element
Element or null
Skips text/comment nodes
lastElementChild
Last child element
Element or null
Skips text/comment nodes
nextSibling
Next sibling node
Node or null
May be text/comment node
previousSibling
Previous sibling node
Node or null
May be text/comment node
nextElementSibling
Next sibling element
Element or null
Skips text/comment nodes
previousElementSibling
Previous sibling element
Element or null
Skips text/comment nodes
childElementCount
Number of child elements
Number
Equivalent to children.length
ownerDocument
Document object that contains node
Document
Useful in iframes
nodeType
Type of node
Number (1-12)
1=Element, 3=Text, 8=Comment
nodeName
Name of node
String
Uppercase for elements
nodeValue
Value of node
String or null
null for elements
Example: DOM traversal patterns
// Parent navigationconst parent = element.parentElement;const grandparent = element.parentElement.parentElement;// Find closest ancestor with classconst container = element.closest(".container");// Child navigationconst allChildren = parent.children; // HTMLCollection of elementsconst firstChild = parent.firstElementChild;const lastChild = parent.lastElementChild;// Sibling navigationconst next = element.nextElementSibling;const previous = element.previousElementSibling;// Iterate all child elementsfor (let child of parent.children) { console.log(child.tagName);}// Node type checkingif (node.nodeType === Node.ELEMENT_NODE) { console.log("Element node");} else if (node.nodeType === Node.TEXT_NODE) { console.log("Text node");}// Walk entire treefunction walkDOM(node, callback) { callback(node); for (let child of node.children) { walkDOM(child, callback); }}walkDOM(document.body, (element) => { console.log(element.tagName);});
Note: Use Element variants (firstElementChild, nextElementSibling) instead of node variants to skip text and comment nodes. This is especially important when HTML has whitespace between elements. children is often more useful than childNodes for the same reason.
1.6 Mutation Observer for DOM Changes
Feature
Syntax/Property
Description
Use Case
MutationObserver Constructor
new MutationObserver(callback)
Creates new observer with callback function. Callback receives array of MutationRecord objects.
Initialize observer
observe
observer.observe(target, options)
Starts observing target node. Options configure what changes to watch. Can observe multiple targets.
Start watching changes
disconnect
observer.disconnect()
Stops observing all targets. Clears pending notifications. Re-observe to resume.
Stop watching
takeRecords
observer.takeRecords()
Returns array of pending mutations not yet processed. Clears mutation queue.
Get pending changes
Option
Type
Description
Required
childList
boolean
Watch for addition/removal of child nodes
At least one must be true
attributes
boolean
Watch for attribute changes on target
Optional
characterData
boolean
Watch for text content changes in target
Optional
subtree
boolean
Extend observation to entire subtree. Applies all options to descendants.
Optional
attributeOldValue
boolean
Record previous attribute value. Requires attributes: true.
Optional
characterDataOldValue
boolean
Record previous text value. Requires characterData: true.
Optional
attributeFilter
string[]
Array of attribute names to watch. Omit to watch all attributes.
Optional
MutationRecord Property
Type
Description
type
string
Type of mutation: "attributes", "characterData", or "childList"
// Watch for specific attribute changesconst observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.attributeName === "data-status") { console.log("Status changed to:", mutation.target.dataset.status); } });});observer.observe(element, { "attributes": true, "attributeFilter": ["data-status", "class"]});// Detect when element is added to DOMfunction whenElementAdded(selector, callback) { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.matches && node.matches(selector)) { callback(node); } }); }); }); observer.observe(document.body, { "childList": true, "subtree": true }); return observer;}// Usageconst obs = whenElementAdded(".dynamic-content", (element) => { console.log("Dynamic content appeared:", element); obs.disconnect(); // Stop after first match});// React to DOM changes with debouncinglet timeoutId;const debouncedObserver = new MutationObserver(() => { clearTimeout(timeoutId); timeoutId = setTimeout(() => { console.log("DOM stable after changes"); updateUI(); }, 300);});debouncedObserver.observe(container, { "childList": true, "subtree": true});
Note: MutationObserver is asynchronous and batches mutations for performance. The callback receives all mutations since last callback. Always disconnect observers when no longer needed to prevent memory leaks. Use attributeFilter to limit observations to specific attributes for better performance.
Warning: Be careful not to modify the DOM inside the observer callback in ways that trigger the same observer - this creates infinite loops. Use disconnect() before making changes and observe() after, or use a flag to prevent recursion.
2. Event Handling and Interaction APIs
2.1 Event Listener Registration and Removal
Method
Syntax
Description
Browser Support
addEventListener
target.addEventListener(type, listener, options)
Registers event listener on target. Supports multiple listeners per event. Third parameter is options object or boolean (useCapture).
Removes event listener. Must match exact function reference and options used in addEventListener.
All Browsers
dispatchEvent
target.dispatchEvent(event)
Dispatches event to target. Returns false if event is cancelable and any handler called preventDefault().
All Browsers
Option
Type
Description
Default
capture
boolean
If true, listener fires during capture phase before target phase
false
once NEW
boolean
If true, listener automatically removed after first invocation
false
passive NEW
boolean
If true, listener will never call preventDefault(). Improves scroll performance.
false
signal NEW
AbortSignal
AbortSignal to remove listener when signal is aborted. Modern alternative to removeEventListener.
undefined
Example: Event listener registration patterns
// Basic event listenerbutton.addEventListener("click", (event) => { console.log("Button clicked!", event);});// Options object - once optionbutton.addEventListener("click", handleClick, { "once": true });// Automatically removed after first click// Passive listener for scroll performancedocument.addEventListener("scroll", handleScroll, { "passive": true });// Cannot call event.preventDefault()// Capture phaseparent.addEventListener("click", handleCapture, { "capture": true });// Fires before child's event// Remove event listenerconst handler = (e) => console.log(e);button.addEventListener("click", handler);button.removeEventListener("click", handler); // Must use same function reference// AbortController for easy removalconst controller = new AbortController();button.addEventListener("click", handleClick, { "signal": controller.signal });button2.addEventListener("click", handleClick2, { "signal": controller.signal });// Remove all listeners at oncecontroller.abort();
Note: Use passive: true for scroll and touch events to improve performance - browser won't wait for listener to finish before scrolling. The once option is perfect for one-time events like splash screens or initial setup. AbortController provides a clean way to remove multiple listeners.
2.2 Event Object Properties and Methods
Property/Method
Type
Description
Use Case
type
string
Event type (e.g., "click", "keydown"). Read-only.
Identify event type
target
EventTarget
Element that originated event. Remains same through bubbling. Read-only.
Get original target
currentTarget
EventTarget
Element with listener attached. Changes during bubbling/capture. Read-only.
Get current handler element
eventPhase
number
Current phase: 0=NONE, 1=CAPTURING, 2=AT_TARGET, 3=BUBBLING. Read-only.
Determine event phase
bubbles
boolean
Indicates if event bubbles up DOM tree. Read-only.
Check if event bubbles
cancelable
boolean
Indicates if event can be canceled. Read-only.
Check if cancelable
defaultPrevented
boolean
True if preventDefault() was called. Read-only.
Check if default prevented
timeStamp
number
Time event was created (milliseconds since page load). Read-only.
Measure timing
isTrusted
boolean
True if event initiated by user action, false if created by script. Read-only.
Verify user interaction
preventDefault()
void
Cancels default browser action if event is cancelable. Does nothing if not cancelable.
Prevent default behavior
stopPropagation()
void
Stops event from bubbling/capturing to other elements. Current element's listeners still fire.
Stop event propagation
stopImmediatePropagation()
void
Stops propagation and prevents other listeners on current element from firing.
Stop all propagation
composedPath()
EventTarget[]
Returns array of elements event will/did traverse. Includes shadow DOM elements.
// Prevent default link navigationlink.addEventListener("click", (event) => { event.preventDefault(); console.log("Navigation prevented");});// Stop event bubblingchild.addEventListener("click", (event) => { event.stopPropagation(); console.log("Won't bubble to parent");});// Stop all propagationelement.addEventListener("click", (event) => { event.stopImmediatePropagation(); console.log("No other listeners will fire");});// Check if default was preventedform.addEventListener("submit", (event) => { if (!event.defaultPrevented) { console.log("Form will submit"); }});
Warning:stopPropagation() prevents parent handlers from executing, which can break event delegation patterns. Use with caution. event.target is the original element while event.currentTarget changes during propagation - common source of bugs.
Note: Use CustomEvent instead of Event when you need to pass data - the detail property is the standard way to attach custom data. Always set bubbles: true unless you specifically want to prevent event propagation. For component communication, custom events are cleaner than callbacks.
2.4 Touch and Pointer Event APIs
Event Type
Description
Fires When
Browser Support
pointerdown
Pointer becomes active (mouse, touch, pen)
Button pressed, screen touched, pen contact
Modern Browsers
pointerup
Pointer becomes inactive
Button released, touch ended, pen lifted
Modern Browsers
pointermove
Pointer position changes
Mouse/touch/pen moves
Modern Browsers
pointercancel
Pointer event canceled by browser
Touch interrupted, too many touches
Modern Browsers
pointerenter
Pointer enters element boundary
Cursor enters, touch starts in element. Doesn't bubble.
Note: Prefer Pointer Events over touch/mouse events - they unify all input types. Use setPointerCapture() to ensure pointer events continue firing even if pointer moves outside element during drag. Set touch-action CSS property to control touch behaviors.
2.5 Keyboard and Mouse Event Patterns
Event Type
Description
Fires When
Cancelable
keydown
Key pressed down
Key initially pressed. Repeats while held.
Yes
keyup
Key released
Key released after press
Yes
keypress DEPRECATED
Character key pressed (deprecated)
Use keydown instead
Yes
click
Element clicked
Mouse down + up on same element
Yes
dblclick
Element double-clicked
Two clicks in quick succession
Yes
mousedown
Mouse button pressed
Button pressed on element
Yes
mouseup
Mouse button released
Button released
Yes
mousemove
Mouse pointer moved
Pointer moves over element
Yes
mouseenter
Mouse enters element
Pointer enters boundary. Doesn't bubble.
No
mouseleave
Mouse leaves element
Pointer leaves boundary. Doesn't bubble.
No
mouseover
Mouse enters element or child
Pointer enters. Bubbles.
Yes
mouseout
Mouse leaves element or child
Pointer leaves. Bubbles.
Yes
contextmenu
Context menu triggered
Right-click or long-press
Yes
wheel
Mouse wheel scrolled
Wheel rotated over element
Yes
KeyboardEvent Property
Type
Description
Example Values
key
string
Value of key pressed. Preferred modern property.
"a", "Enter", "ArrowLeft"
code
string
Physical key code. Independent of keyboard layout.
"KeyA", "Enter", "ArrowLeft"
keyCode DEPRECATED
number
Numeric key code (deprecated). Use key or code instead.
65, 13, 37
ctrlKey
boolean
True if Ctrl/Command key pressed during event
true / false
shiftKey
boolean
True if Shift key pressed during event
true / false
altKey
boolean
True if Alt/Option key pressed during event
true / false
metaKey
boolean
True if Meta key pressed (Windows/Command key)
true / false
repeat
boolean
True if key held down (auto-repeat)
true / false
MouseEvent Property
Type
Description
Values
button
number
Which button was pressed
0=Left, 1=Middle, 2=Right, 3=Back, 4=Forward
buttons
number
Bitmask of currently pressed buttons
1=Left, 2=Right, 4=Middle (can combine)
clientX / clientY
number
Coordinates relative to viewport
Pixel values
pageX / pageY
number
Coordinates relative to document
Pixel values
screenX / screenY
number
Coordinates relative to screen
Pixel values
offsetX / offsetY
number
Coordinates relative to target element
Pixel values
movementX / movementY
number
Distance moved since last mousemove
Delta in pixels
relatedTarget
Element
Secondary target (for mouseenter/mouseleave)
Element reference
Example: Keyboard event handling
// Modern key detectiondocument.addEventListener("keydown", (event) => { // Use 'key' property (preferred) if (event.key === "Enter") { console.log("Enter pressed"); } // Use 'code' for physical key position if (event.code === "KeyA") { console.log("A key pressed (QWERTY position)"); } // Modifier keys if (event.ctrlKey && event.key === "s") { event.preventDefault(); console.log("Ctrl+S (Save)"); } // Check for key combinations if (event.shiftKey && event.altKey && event.key === "K") { console.log("Shift+Alt+K pressed"); } // Arrow keys switch(event.key) { case "ArrowUp": moveUp(); break; case "ArrowDown": moveDown(); break; case "ArrowLeft": moveLeft(); break; case "ArrowRight": moveRight(); break; } // Prevent auto-repeat if (event.repeat) return;});
Note: Always use event.key instead of deprecated keyCode. Use mouseenter/mouseleave instead of mouseover/mouseout when you don't want events from child elements. For keyboard shortcuts, check modifier keys with ctrlKey, shiftKey, altKey, metaKey.
2.6 Event Delegation and Event Bubbling
Concept
Description
Benefits
Use Case
Event Bubbling
Events propagate from target element up through ancestors to document root
Enables event delegation. Single listener handles multiple elements.
Default event flow in DOM
Event Capturing
Events propagate from document root down to target element (before bubbling)
Intercept events before target. Use capture: true option.
Special interception needs
Event Delegation
Single listener on parent handles events from multiple children via bubbling
Better performance. Works with dynamic content. Less memory.
Lists, tables, dynamic UI
Event Target
event.target is element that triggered event (child)
Identify actual clicked element in delegation pattern
Determine source element
Current Target
event.currentTarget is element with listener attached (parent)
Reference to delegating element
Access parent in delegation
Event Phase
Value
Description
Direction
NONE
0
Event not being processed
N/A
CAPTURING_PHASE
1
Event traveling from root to target
Top-down
AT_TARGET
2
Event reached target element
At target
BUBBLING_PHASE
3
Event traveling from target to root
Bottom-up
Example: Event delegation pattern
// Instead of attaching listener to each item// ❌ Bad: Many listeners, doesn't work with dynamic contentdocument.querySelectorAll(".item").forEach((item) => { item.addEventListener("click", handleClick);});// ✅ Good: Single listener on parentconst list = document.querySelector("#list");list.addEventListener("click", (event) => { // Check if clicked element matches selector if (event.target.matches(".item")) { console.log("Item clicked:", event.target); handleItem(event.target); } // Or use closest for nested elements const item = event.target.closest(".item"); if (item && list.contains(item)) { console.log("Item found:", item); }});// Works automatically with dynamically added itemsconst newItem = document.createElement("div");newItem.className = "item";list.appendChild(newItem); // Click handler works immediately!
// Multi-action delegationdocument.querySelector("#toolbar").addEventListener("click", (event) => { const button = event.target.closest("button"); if (!button) return; const action = button.dataset.action; switch(action) { case "save": handleSave(); break; case "delete": handleDelete(); break; case "edit": handleEdit(); break; }});// Table row delegationtable.addEventListener("click", (event) => { const row = event.target.closest("tr"); if (!row || row.parentElement.tagName === "THEAD") return; const cell = event.target.closest("td"); const columnIndex = Array.from(row.cells).indexOf(cell); console.log("Row:", row.rowIndex); console.log("Column:", columnIndex); console.log("Data:", row.dataset.id);});// Form delegationform.addEventListener("input", (event) => { const input = event.target; if (input.matches("[data-validate]")) { validateField(input); }});// Performance: Delegate to document for global handlersdocument.addEventListener("click", (event) => { // Close dropdowns when clicking outside if (!event.target.closest(".dropdown")) { closeAllDropdowns(); }});
Note: Event delegation is essential for performance with large lists (hundreds/thousands of items). It automatically handles dynamically added elements without re-attaching listeners. Use event.target.closest(selector) to handle clicks on nested elements within delegated items.
Warning: Not all events bubble! Events like focus, blur, mouseenter, mouseleave don't bubble. Use their bubbling alternatives: focusin, focusout, mouseover, mouseout for delegation. Always check if event.target matches your selector before acting.
Event Handling Best Practices
Use addEventListener with options object for modern features (once, passive, signal)
Prefer Pointer Events over separate mouse/touch handlers for unified input
Use event delegation for lists and dynamic content - better performance and automatic handling of new elements
Set passive: true for scroll/touch events to improve performance
Use event.key instead of deprecated keyCode for keyboard events
Remember: event.target = original element, event.currentTarget = element with listener
Call preventDefault() to stop default browser actions, stopPropagation() to prevent bubbling
Use CustomEvent with detail property for component communication
Clean up listeners with AbortController or removeEventListener to prevent memory leaks
3. Network Communication APIs
3.1 Fetch API and Request/Response Objects
Method/Property
Syntax
Description
Browser Support
fetch
fetch(url, options)
Makes HTTP request, returns Promise<Response>. Modern replacement for XMLHttpRequest.
Modern Browsers
Request Constructor
new Request(url, options)
Creates Request object that can be reused. Options include method, headers, body, mode, credentials.
Modern Browsers
Response Constructor
new Response(body, options)
Creates Response object. Options include status, statusText, headers. Used in Service Workers.
Note: Fetch doesn't reject on HTTP errors (404, 500) - check response.ok or response.status. Body can only be read once - use response.clone() if you need to read it multiple times. Default credentials: "same-origin" doesn't send cookies to different origins.
3.2 Fetch API Advanced Patterns (AbortController, Streams)
Feature
Syntax
Description
Use Case
AbortController
new AbortController()
Creates controller for aborting fetch requests. Has signal property and abort() method.
Cancel requests
signal
controller.signal
AbortSignal to pass to fetch. Automatically aborts when controller.abort() called.
Link abort to fetch
abort()
controller.abort(reason)
Aborts associated request. Optional reason passed to error handler.
Trigger cancellation
ReadableStream
response.body
Response body as ReadableStream. Allows progressive reading of large responses.
Stream large data
getReader()
stream.getReader()
Gets ReadableStreamDefaultReader to read chunks. Locks stream.
Read stream chunks
read()
reader.read()
Returns Promise<{value, done}>. Value is Uint8Array chunk.
// Stream large file download with progressasync function downloadWithProgress(url) { const response = await fetch(url); const contentLength = response.headers.get("content-length"); const total = parseInt(contentLength, 10); let loaded = 0; const reader = response.body.getReader(); const chunks = []; while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); loaded += value.length; const progress = (loaded / total) * 100; console.log(`Progress: ${progress.toFixed(2)}%`); } // Concatenate chunks into single Uint8Array const chunksAll = new Uint8Array(loaded); let position = 0; for (let chunk of chunks) { chunksAll.set(chunk, position); position += chunk.length; } return chunksAll;}// Stream text data line by lineasync function streamText(url) { const response = await fetch(url); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { done, value } = await reader.read(); if (done) break; const text = decoder.decode(value, { "stream": true }); console.log("Chunk:", text); }}
Example: Advanced patterns
// Request deduplicationconst requestCache = new Map();async function fetchOnce(url) { if (requestCache.has(url)) { return requestCache.get(url); } const promise = fetch(url).then((r) => r.json()); requestCache.set(url, promise); try { return await promise; } catch (error) { requestCache.delete(url); // Remove failed request throw error; }}// Retry with exponential backoffasync function fetchWithRetry(url, options = {}, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { const response = await fetch(url, options); if (!response.ok && i < maxRetries - 1) { throw new Error(`HTTP ${response.status}`); } return response; } catch (error) { if (i === maxRetries - 1) throw error; const delay = Math.pow(2, i) * 1000; // 1s, 2s, 4s await new Promise((resolve) => setTimeout(resolve, delay)); } }}// Parallel requests with Promise.allasync function fetchMultiple(urls) { const promises = urls.map((url) => fetch(url).then((r) => r.json()) ); return Promise.all(promises);}// Race condition - first to respond winsasync function fetchFastest(urls) { const promises = urls.map((url) => fetch(url).then((r) => r.json()) ); return Promise.race(promises);}
Warning:AbortController.abort() throws an error in the fetch promise - always catch it. Streams can only be read once - lock is not released until reader is closed. When streaming, make sure to handle errors and cleanup properly to avoid memory leaks.
3.3 XMLHttpRequest and Legacy AJAX Patterns
Property/Method
Description
Modern Alternative
new XMLHttpRequest()
Creates XHR object for AJAX requests
Use fetch()
open(method, url, async)
Initializes request. Third parameter is async flag (default true).
fetch(url, {method})
send(body)
Sends request. Body for POST/PUT.
fetch(url, {body})
abort()
Aborts request in progress
AbortController
setRequestHeader(name, value)
Sets HTTP request header. Call after open(), before send().
// GET requestconst xhr = new XMLHttpRequest();xhr.open("GET", "/api/users", true);xhr.onload = function() { if (xhr.status === 200) { const data = JSON.parse(xhr.responseText); console.log("Success:", data); } else { console.error("Error:", xhr.status); }};xhr.onerror = function() { console.error("Network error");};xhr.send();// POST request with JSONconst xhr2 = new XMLHttpRequest();xhr2.open("POST", "/api/users", true);xhr2.setRequestHeader("Content-Type", "application/json");xhr2.onreadystatechange = function() { if (xhr2.readyState === 4) { // DONE if (xhr2.status === 201) { console.log("Created:", JSON.parse(xhr2.responseText)); } }};xhr2.send(JSON.stringify({ "name": "John" }));// Upload with progressconst formData = new FormData();formData.append("file", file);const xhr3 = new XMLHttpRequest();xhr3.upload.addEventListener("progress", (event) => { if (event.lengthComputable) { const percent = (event.loaded / event.total) * 100; console.log(`Upload: ${percent.toFixed(2)}%`); }});xhr3.open("POST", "/api/upload", true);xhr3.send(formData);
Note: XMLHttpRequest is legacy API - use Fetch API for new projects. XHR still useful for upload progress tracking (Fetch doesn't support this natively yet). XHR requires more boilerplate code and callback-based error handling compared to Promise-based Fetch.
3.4 WebSocket API for Real-time Communication
Feature
Syntax
Description
Browser Support
WebSocket Constructor
new WebSocket(url, protocols)
Creates WebSocket connection. URL must start with ws:// or wss://. Optional protocols array.
All Browsers
send(data)
ws.send(data)
Sends data to server. Accepts string, Blob, ArrayBuffer, ArrayBufferView.
All Browsers
close(code, reason)
ws.close(code, reason)
Closes connection. Optional close code and reason string.
All Browsers
Property
Type
Description
Values
readyState
number
Connection state
0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED
url
string
WebSocket URL
Read-only
protocol
string
Negotiated subprotocol
Read-only
bufferedAmount
number
Bytes queued but not yet transmitted
Read-only
binaryType
string
Binary data type received
"blob" or "arraybuffer"
Event
Description
Event Object Properties
open
Connection established successfully
Standard Event object
message
Message received from server
data: message content (string, Blob, ArrayBuffer)
error
Error occurred
Standard Event object
close
Connection closed
code: close code, reason: close reason, wasClean: boolean
Example: WebSocket basic usage
// Create WebSocket connectionconst ws = new WebSocket("wss://example.com/socket");// Connection openedws.addEventListener("open", (event) => { console.log("Connected to server"); ws.send("Hello Server!");});// Listen for messagesws.addEventListener("message", (event) => { console.log("Message from server:", event.data); // Handle different data types if (typeof event.data === "string") { const message = JSON.parse(event.data); handleMessage(message); } else if (event.data instanceof Blob) { // Handle binary data event.data.text().then((text) => console.log(text)); }});// Handle errorsws.addEventListener("error", (event) => { console.error("WebSocket error:", event);});// Connection closedws.addEventListener("close", (event) => { console.log("Connection closed:", event.code, event.reason); if (event.wasClean) { console.log("Clean close"); } else { console.log("Connection died"); }});// Send different data typesws.send("Text message");ws.send(JSON.stringify({ "type": "chat", "message": "Hello" }));ws.send(new Blob(["binary data"]));ws.send(new Uint8Array([1, 2, 3, 4]));// Close connection gracefullyws.close(1000, "Normal closure");
Note: Always use wss:// (secure WebSocket) in production, not ws://. Check readyState before calling send() to avoid errors. Implement reconnection logic for production applications. Use bufferedAmount to check if send buffer is full.
3.5 Server-Sent Events (EventSource) API
Feature
Syntax
Description
Browser Support
EventSource Constructor
new EventSource(url, options)
Creates server-sent events connection. One-way: server to client only.
Note: EventSource automatically reconnects on connection loss (WebSocket doesn't). SSE is one-way only (server to client) - use WebSocket for bidirectional communication. SSE uses regular HTTP, works through most proxies and firewalls. Default retry timeout is 3 seconds.
3.6 Navigator.sendBeacon for Analytics
Feature
Syntax
Description
Browser Support
sendBeacon
navigator.sendBeacon(url, data)
Sends asynchronous POST request that completes even if page is unloading. Returns boolean indicating if queued successfully.
Note:sendBeacon is designed for analytics and diagnostics that must not be lost when page unloads. Request completes even if user closes tab/window. Cannot read response or handle errors. Has size limits (typically 64KB). Uses POST method only.
Warning: Don't use sendBeacon for critical data that requires confirmation - there's no way to know if request succeeded. Browsers may limit total beacon data size. Use Blob with proper Content-Type for JSON data, not plain string.
Network Communication Best Practices
Use Fetch API for modern HTTP requests - cleaner syntax and Promise-based
Always check response.ok or response.status - fetch doesn't reject on HTTP errors
Use AbortController for request cancellation and timeouts
Implement proper error handling with try/catch for async/await patterns
Use WebSocket for bidirectional real-time communication (chat, live updates)
Use EventSource for one-way server-to-client streaming (notifications, live feeds)
Implement reconnection logic for WebSocket connections in production
Use credentials: "include" to send cookies with cross-origin requests
Use navigator.sendBeacon for analytics during page unload - guaranteed delivery
Stream large responses with ReadableStream for memory efficiency and progress tracking
4. Storage and Persistence APIs
4.1 localStorage and sessionStorage Operations
Method
Syntax
Description
Scope
setItem
storage.setItem(key, value)
Stores key-value pair. Value converted to string. Throws QuotaExceededError if storage full.
Both
getItem
storage.getItem(key)
Retrieves value by key. Returns null if key doesn't exist.
Both
removeItem
storage.removeItem(key)
Removes key-value pair. No error if key doesn't exist.
Both
clear
storage.clear()
Removes all key-value pairs from storage.
Both
key
storage.key(index)
Returns key name at index position. Returns null if out of range.
Both
length
storage.length
Number of stored key-value pairs. Read-only property.
Both
Storage Type
Lifetime
Capacity
Use Case
localStorage
Persists until explicitly deleted. Survives browser restart.
~5-10MB per origin
User preferences, settings, cached data
sessionStorage
Cleared when page session ends (tab/window closed). Survives page reload.
~5-10MB per origin
Temporary data, form state, single-session cache
Event Property
Type
Description
storage event
StorageEvent
Fires on other tabs/windows when storage changes. Doesn't fire in tab that made change.
key
string
Key that was changed. null if clear() called.
oldValue
string
Previous value. null if new key.
newValue
string
New value. null if item removed.
url
string
URL of page that made the change.
storageArea
Storage
Reference to localStorage or sessionStorage affected.
Example: Basic storage operations
// Store datalocalStorage.setItem("username", "john_doe");localStorage.setItem("theme", "dark");// Store objects (must stringify)const user = { "id": 123, "name": "John" };localStorage.setItem("user", JSON.stringify(user));// Retrieve dataconst username = localStorage.getItem("username");console.log(username); // "john_doe"// Retrieve and parse objectsconst userStr = localStorage.getItem("user");const userData = JSON.parse(userStr);console.log(userData.name); // "John"// Remove itemlocalStorage.removeItem("theme");// Clear alllocalStorage.clear();// Check if key existsif (localStorage.getItem("username") !== null) { console.log("Username exists");}// Iterate all keysfor (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); const value = localStorage.getItem(key); console.log(`${key}: ${value}`);}
Example: Storage event for cross-tab communication
// Listen for storage changes from other tabswindow.addEventListener("storage", (event) => { console.log("Storage changed in another tab"); console.log("Key:", event.key); console.log("Old value:", event.oldValue); console.log("New value:", event.newValue); console.log("URL:", event.url); console.log("Storage area:", event.storageArea === localStorage); // React to specific changes if (event.key === "theme") { applyTheme(event.newValue); } // Handle logout in all tabs if (event.key === "isLoggedIn" && event.newValue === "false") { redirectToLogin(); }});// Trigger storage event in other tabslocalStorage.setItem("notification", JSON.stringify({ "message": "New update available", "timestamp": Date.now()}));
Note: Web Storage only stores strings - use JSON.stringify() and JSON.parse() for objects. Storage is synchronous and can block UI - use IndexedDB for large data. Data is scoped to origin (protocol + domain + port). Storage event doesn't fire in the tab that made the change.
Warning: Storage can throw QuotaExceededError when full - always wrap in try/catch. Private browsing modes may have reduced or disabled storage. Never store sensitive data (passwords, tokens) in localStorage - vulnerable to XSS. Use sessionStorage for temporary data that shouldn't persist.
4.2 IndexedDB Database Operations and Transactions
Method
Syntax
Description
Browser Support
open
indexedDB.open(name, version)
Opens database. Returns IDBOpenDBRequest. Triggers upgradeneeded if version changes.
Note: IndexedDB is asynchronous and event-based. Schema changes (createObjectStore, createIndex) only allowed in upgradeneeded event. Use put() for upsert (insert or update), add() fails if key exists. IndexedDB can store large amounts of data (hundreds of MB to GB depending on browser).
Warning: All IndexedDB operations are transactional. Transaction auto-commits when all requests complete. Don't perform async operations (fetch, setTimeout) inside transaction handlers - transaction will close. Use indexes for queries - full table scans with cursors are slow on large datasets.
4.3 Cache API for Service Worker Integration
Method
Syntax
Description
Browser Support
caches.open
caches.open(cacheName)
Opens named cache. Creates if doesn't exist. Returns Promise<Cache>.
Modern Browsers
caches.match
caches.match(request, options)
Searches all caches for matching request. Returns Promise<Response> or undefined.
Modern Browsers
caches.has
caches.has(cacheName)
Checks if named cache exists. Returns Promise<boolean>.
Modern Browsers
caches.delete
caches.delete(cacheName)
Deletes named cache. Returns Promise<boolean> indicating if cache existed.
Modern Browsers
caches.keys
caches.keys()
Returns Promise with array of cache names.
Modern Browsers
Cache Method
Description
Use Case
add
Fetches URL and stores response. Single request.
Cache single resource
addAll
Fetches array of URLs and stores responses. Atomic - fails if any fetch fails.
Cache multiple resources
put
Stores request-response pair directly. Full control over what's cached.
Cache custom responses
match
Searches cache for matching request. Returns Promise<Response> or undefined.
Retrieve cached response
matchAll
Returns all matching responses. Optional request parameter for filtering.
Returns all request keys in cache. Optional request parameter for filtering.
List cached URLs
Example: Basic Cache API usage
// Open cache and add resourcesasync function cacheResources() { const cache = await caches.open("my-cache-v1"); // Add single resource await cache.add("/api/data"); // Add multiple resources (atomic operation) await cache.addAll([ "/", "/styles.css", "/script.js", "/logo.png" ]); console.log("Resources cached");}// Check cache before fetchingasync function fetchWithCache(url) { // Try cache first const cachedResponse = await caches.match(url); if (cachedResponse) { console.log("Cache hit:", url); return cachedResponse; } // Cache miss - fetch from network console.log("Cache miss:", url); const response = await fetch(url); // Store in cache for next time const cache = await caches.open("my-cache-v1"); cache.put(url, response.clone()); return response;}// Usageconst response = await fetchWithCache("/api/users");const data = await response.json();
Example: Cache strategies
// Cache First (good for static assets)async function cacheFirst(request) { const cached = await caches.match(request); if (cached) return cached; const response = await fetch(request); const cache = await caches.open("static-v1"); cache.put(request, response.clone()); return response;}// Network First (good for dynamic data)async function networkFirst(request) { try { const response = await fetch(request); const cache = await caches.open("dynamic-v1"); cache.put(request, response.clone()); return response; } catch (error) { const cached = await caches.match(request); if (cached) return cached; throw error; }}// Stale While Revalidate (best of both)async function staleWhileRevalidate(request) { const cached = await caches.match(request); const fetchPromise = fetch(request).then((response) => { const cache = caches.open("swr-v1"); cache.then((c) => c.put(request, response.clone())); return response; }); return cached || fetchPromise;}// Cache with expirationasync function cacheWithExpiry(request, cacheName, maxAge) { const cache = await caches.open(cacheName); const cached = await cache.match(request); if (cached) { const cachedDate = new Date(cached.headers.get("date")); const age = Date.now() - cachedDate.getTime(); if (age < maxAge) { return cached; // Still fresh } } // Expired or missing - fetch fresh const response = await fetch(request); cache.put(request, response.clone()); return response;}
Example: Cache management
// List all cachesasync function listCaches() { const cacheNames = await caches.keys(); console.log("Caches:", cacheNames); return cacheNames;}// Delete old cache versionsasync function deleteOldCaches(currentVersion) { const cacheNames = await caches.keys(); const deletePromises = cacheNames .filter((name) => name !== currentVersion) .map((name) => caches.delete(name)); await Promise.all(deletePromises); console.log("Old caches deleted");}// Usage in Service Worker activate eventself.addEventListener("activate", (event) => { event.waitUntil( deleteOldCaches("my-cache-v2") );});// List cached URLsasync function listCachedUrls(cacheName) { const cache = await caches.open(cacheName); const requests = await cache.keys(); const urls = requests.map((req) => req.url); console.log("Cached URLs:", urls); return urls;}// Remove specific cached itemasync function removeCachedItem(url) { const cache = await caches.open("my-cache-v1"); const deleted = await cache.delete(url); console.log(`Cache entry deleted: ${deleted}`);}// Clear all cachesasync function clearAllCaches() { const cacheNames = await caches.keys(); await Promise.all( cacheNames.map((name) => caches.delete(name)) ); console.log("All caches cleared");}
Note: Cache API stores Request-Response pairs, not just data. Primarily used with Service Workers for offline functionality. Responses must be clone()d before caching because Response body can only be read once. Cache is persistent and separate from HTTP cache.
4.4 Cookie Manipulation with document.cookie
Property
Description
Format
document.cookie
Gets all cookies as semicolon-separated string. Setting adds/updates single cookie.
"name=value; name2=value2"
Cookie Attribute
Description
Example
expires
Expiration date (GMT format). Cookie deleted after this date.
expires=Wed, 01 Jan 2025 00:00:00 GMT
max-age
Lifetime in seconds. Overrides expires. Negative value deletes cookie.
max-age=3600
path
URL path where cookie is accessible. Defaults to current path.
path=/
domain
Domain where cookie is accessible. Includes subdomains if specified.
domain=.example.com
secure
Cookie only sent over HTTPS. Essential for sensitive data.
secure
httpOnly
Cookie inaccessible to JavaScript (document.cookie). Only server-side. Set by server.
// Set cookiefunction setCookie(name, value, days = 7, options = {}) { let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`; // Expiration if (days) { const date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); cookie += `; expires=${date.toUTCString()}`; } // Path (default to root) cookie += `; path=${options.path || "/"}`; // Domain if (options.domain) { cookie += `; domain=${options.domain}`; } // Secure if (options.secure) { cookie += "; secure"; } // SameSite if (options.sameSite) { cookie += `; samesite=${options.sameSite}`; } document.cookie = cookie;}// Get cookiefunction getCookie(name) { const cookies = document.cookie.split("; "); for (let cookie of cookies) { const [cookieName, cookieValue] = cookie.split("="); if (decodeURIComponent(cookieName) === name) { return decodeURIComponent(cookieValue); } } return null;}// Delete cookiefunction deleteCookie(name, options = {}) { setCookie(name, "", -1, options);}// Get all cookies as objectfunction getAllCookies() { const cookies = {}; document.cookie.split("; ").forEach((cookie) => { const [name, value] = cookie.split("="); cookies[decodeURIComponent(name)] = decodeURIComponent(value); }); return cookies;}// UsagesetCookie("username", "john_doe", 30, { "path": "/", "secure": true, "sameSite": "Strict"});const username = getCookie("username");console.log(username); // "john_doe"deleteCookie("username");
Note: Cookies have 4KB size limit per cookie. Always use encodeURIComponent() for names and values. HttpOnly cookies can't be accessed via JavaScript - only set by server. Use SameSite=Strict or Lax for CSRF protection.
Warning: Cookies are sent with every HTTP request to the domain - impacts performance. Don't store sensitive data in client-accessible cookies. Secure flag is mandatory for SameSite=None. Deleting cookie requires matching path and domain of original cookie.
4.5 Origin Private File System API
Method
Syntax
Description
Browser Support
getDirectory NEW
navigator.storage.getDirectory()
Returns Promise<FileSystemDirectoryHandle> for origin's private file system root.
Modern Browsers
getFileHandle
dir.getFileHandle(name, options)
Gets file handle. Options: create: true to create if missing.
Modern Browsers
getDirectoryHandle
dir.getDirectoryHandle(name, options)
Gets subdirectory handle. Options: create: true to create if missing.
Modern Browsers
removeEntry
dir.removeEntry(name, options)
Deletes file or directory. Options: recursive: true for directories.
Modern Browsers
getFile
fileHandle.getFile()
Returns Promise<File> object with file data.
Modern Browsers
createWritable
fileHandle.createWritable()
Returns Promise<FileSystemWritableFileStream> for writing.
Note: Origin Private File System is private to origin and not accessible to user. Good for app-specific data, cache, temporary files. Not for user documents - use File System Access API for that. Storage is persistent and survives browser restarts.
4.6 Storage Quota and Usage Estimation
Method
Syntax
Description
Browser Support
estimate
navigator.storage.estimate()
Returns Promise with quota and usage info. Properties: quota, usage.
Modern Browsers
persist
navigator.storage.persist()
Requests persistent storage (won't be cleared under pressure). Returns Promise<boolean>.
Modern Browsers
persisted
navigator.storage.persisted()
Checks if storage is persistent. Returns Promise<boolean>.
Note: Quota varies by browser and available disk space (typically 10-50% of available disk). persist() requires user interaction or installed PWA. Persistent storage won't be cleared under storage pressure. Usage includes IndexedDB, Cache API, and File System API data.
Storage API Best Practices
Use localStorage for small, simple data (settings, preferences) - synchronous and limited to 5-10MB
Use IndexedDB for large structured data (offline databases, cached content) - asynchronous and scalable
Use Cache API with Service Workers for offline-first applications and resource caching
Always wrap storage operations in try/catch - quota exceeded and private mode can cause errors
Use JSON.stringify/parse for complex objects in localStorage
Set appropriate cookie attributes: Secure, HttpOnly, SameSite
Request persistent storage for critical data with navigator.storage.persist()
Monitor quota usage with navigator.storage.estimate() before large operations
Never store sensitive data (passwords, tokens) in client-side storage - vulnerable to XSS
Consider data expiration strategies for cached content to prevent stale data
5. Media and Graphics APIs
5.1 HTMLMediaElement (Audio/Video) Control API
Property
Type
Description
Read-Only
src
string
URL of media resource. Can be set to load new media.
No
currentTime
number
Current playback position in seconds. Set to seek.
No
duration
number
Total duration in seconds. NaN if unknown.
Yes
paused
boolean
true if media is paused. Use play()/pause() to change.
Yes
ended
boolean
true if media has finished playing.
Yes
volume
number
Volume level 0.0 to 1.0. Throws error if out of range.
No
muted
boolean
Whether audio is muted. Doesn't affect volume property.
No
playbackRate
number
Playback speed. 1.0 is normal, 2.0 is 2x, 0.5 is half speed.
Note:play() returns a Promise - use async/await or .then/.catch for error handling. Autoplay is restricted by browsers - user interaction often required. Use canPlayType() to check format support before loading. timeupdate fires frequently - throttle expensive operations.
5.2 MediaStream and getUserMedia for Camera/Microphone
Method
Syntax
Description
Browser Support
getUserMedia
navigator.mediaDevices.getUserMedia(constraints)
Requests camera/microphone access. Returns Promise<MediaStream>. Requires HTTPS or localhost.
Note:getUserMedia requires HTTPS (or localhost for development). User must grant permission - handle denial gracefully. Always stop() tracks when done to release camera/microphone. Setting enabled = false mutes temporarily; stop() releases permanently.
Warning: Screen capture (getDisplayMedia) requires user gesture - can't be called on page load. Don't request more permissions than needed - ask for audio OR video separately if possible. Some constraints are mandatory (exact) and will fail if not supported - use ideal for preferences.
5.3 MediaRecorder API for Recording Media
Constructor
Description
Browser Support
MediaRecorder
new MediaRecorder(stream, options) - Creates recorder for MediaStream. Options include mimeType, audioBitsPerSecond, videoBitsPerSecond.
All Browsers
Method
Description
When to Use
start(timeslice)
Starts recording. Optional timeslice in ms for data chunks.
Begin recording
stop()
Stops recording. Fires dataavailable and stop events.
End recording
pause()
Pauses recording without ending. Can resume().
Temporary pause
resume()
Resumes paused recording.
Continue after pause
requestData()
Forces dataavailable event with current buffer.
Get data during recording
Property
Type
Description
state
string
"inactive", "recording", or "paused". Read-only.
mimeType
string
MIME type being used (e.g., "video/webm"). Read-only.
Note: MediaRecorder support varies by browser - use MediaRecorder.isTypeSupported(mimeType) to check. Common formats: video/webm (Chrome/Firefox), video/mp4 (Safari). Use timeslice parameter in start() to get data in chunks for streaming or progress tracking.
Warning: Recording consumes memory - collect chunks in dataavailable events. Large recordings can cause memory issues - consider chunked uploads. Always stop tracks when done to release camera/microphone. Some browsers limit recording duration or file size.
5.4 Web Audio API for Audio Processing
Node Type
Purpose
Common Use
AudioContext
Main audio processing graph. All nodes connect through context.
// Create analyzer for visualizationfunction createVisualizer(sourceNode) { const analyser = audioContext.createAnalyser(); analyser.fftSize = 2048; // 256, 512, 1024, 2048, 4096, etc. sourceNode.connect(analyser); analyser.connect(audioContext.destination); const bufferLength = analyser.frequencyBinCount; const dataArray = new Uint8Array(bufferLength); return { analyser, dataArray };}// Draw waveformfunction drawWaveform(analyser, dataArray, canvas) { const ctx = canvas.getContext("2d"); const width = canvas.width; const height = canvas.height; function draw() { requestAnimationFrame(draw); // Get time domain data analyser.getByteTimeDomainData(dataArray); // Clear canvas ctx.fillStyle = "rgb(0, 0, 0)"; ctx.fillRect(0, 0, width, height); // Draw waveform ctx.lineWidth = 2; ctx.strokeStyle = "rgb(0, 255, 0)"; ctx.beginPath(); const sliceWidth = width / dataArray.length; let x = 0; for (let i = 0; i < dataArray.length; i++) { const v = dataArray[i] / 128.0; const y = v * height / 2; if (i === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } x += sliceWidth; } ctx.lineTo(width, height / 2); ctx.stroke(); } draw();}// Draw frequency barsfunction drawFrequencyBars(analyser, dataArray, canvas) { const ctx = canvas.getContext("2d"); const width = canvas.width; const height = canvas.height; function draw() { requestAnimationFrame(draw); // Get frequency data analyser.getByteFrequencyData(dataArray); ctx.fillStyle = "rgb(0, 0, 0)"; ctx.fillRect(0, 0, width, height); const barWidth = (width / dataArray.length) * 2.5; let x = 0; for (let i = 0; i < dataArray.length; i++) { const barHeight = (dataArray[i] / 255) * height; ctx.fillStyle = `rgb(${dataArray[i] + 100}, 50, 50)`; ctx.fillRect(x, height - barHeight, barWidth, barHeight); x += barWidth + 1; } } draw();}
Note: AudioContext may be suspended on page load - resume with user gesture: audioContext.resume(). All timing uses audioContext.currentTime (high precision). Audio nodes are one-time use - create new nodes for each playback. Use AudioParam methods (setValueAtTime, linearRampToValueAtTime) for smooth parameter changes.
// Get pixel dataconst imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);const pixels = imageData.data; // Uint8ClampedArray [r,g,b,a, r,g,b,a, ...]// Grayscale filterfor (let i = 0; i < pixels.length; i += 4) { const r = pixels[i]; const g = pixels[i + 1]; const b = pixels[i + 2]; const gray = 0.299 * r + 0.587 * g + 0.114 * b; pixels[i] = gray; // Red pixels[i + 1] = gray; // Green pixels[i + 2] = gray; // Blue // pixels[i + 3] is alpha (unchanged)}// Put modified pixels backctx.putImageData(imageData, 0, 0);// Invert colorsfor (let i = 0; i < pixels.length; i += 4) { pixels[i] = 255 - pixels[i]; // Red pixels[i + 1] = 255 - pixels[i + 1]; // Green pixels[i + 2] = 255 - pixels[i + 2]; // Blue}ctx.putImageData(imageData, 0, 0);// Increase brightnessconst brightness = 50;for (let i = 0; i < pixels.length; i += 4) { pixels[i] += brightness; // Red pixels[i + 1] += brightness; // Green pixels[i + 2] += brightness; // Blue}ctx.putImageData(imageData, 0, 0);
Note: Canvas operations are immediate mode - no scene graph. Use requestAnimationFrame for smooth animations. save() and restore() manage state stack (styles, transforms). Canvas is raster-based - scales poorly; use CSS for sizing and set canvas.width/height for resolution.
5.6 WebGL API for 3D Graphics Rendering
WebGL Concept
Description
Purpose
Context
canvas.getContext("webgl") or "webgl2"
Initialize WebGL rendering context
Shader
GLSL programs (vertex and fragment shaders)
GPU-side rendering logic
Program
Linked vertex + fragment shader pair
Complete rendering pipeline
Buffer
GPU memory for vertex data (positions, colors, UVs)
// Vertex shader with UVsconst vertexShaderSource = ` attribute vec2 a_position; attribute vec2 a_texCoord; varying vec2 v_texCoord; void main() { gl_Position = vec4(a_position, 0.0, 1.0); v_texCoord = a_texCoord; }`;// Fragment shader with textureconst fragmentShaderSource = ` precision mediump float; varying vec2 v_texCoord; uniform sampler2D u_texture; void main() { gl_FragColor = texture2D(u_texture, v_texCoord); }`;// Quad vertices (2 triangles)const positions = new Float32Array([ -0.5, 0.5, // Top left -0.5, -0.5, // Bottom left 0.5, 0.5, // Top right 0.5, -0.5 // Bottom right]);// Texture coordinatesconst texCoords = new Float32Array([ 0.0, 0.0, // Top left 0.0, 1.0, // Bottom left 1.0, 0.0, // Top right 1.0, 1.0 // Bottom right]);// Create texturefunction loadTexture(gl, url) { const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); // Temporary 1x1 blue pixel gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255]) ); // Load image const image = new Image(); image.onload = () => { gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); // Set filtering gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); }; image.src = url; return texture;}// Draw textured quadgl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
Note: WebGL is low-level - consider libraries like Three.js, Babylon.js for easier 3D. WebGL 2 has better features but less browser support than WebGL 1. Always check for WebGL support: !!canvas.getContext("webgl"). Shaders are written in GLSL (OpenGL Shading Language).
Warning: WebGL can fail on old hardware or when GPU is unavailable. Always check context creation. Shader compilation can fail - always check gl.getShaderParameter and gl.getProgramParameter. WebGL state is global - careful with state management in complex apps.
Media and Graphics API Best Practices
Use play().catch() to handle autoplay failures gracefully
Always stop() media tracks when done to release camera/microphone hardware
Request minimal permissions - audio OR video separately if possible
Use MediaRecorder.isTypeSupported() to check format support before recording
For Web Audio, resume AudioContext with user gesture: audioContext.resume()
Connect audio nodes before starting playback - nodes can't be reconnected after start
Use requestAnimationFrame for smooth Canvas and WebGL animations
Save/restore Canvas state with ctx.save() and ctx.restore() for clean transforms
For Canvas, set canvas.width and canvas.height for resolution, use CSS for display size
Consider Three.js or Babylon.js instead of raw WebGL for complex 3D scenes
Always check WebGL shader compilation and program linking for errors
Use getUserMedia constraints wisely - ideal values are preferred, not mandatory
Note: Geolocation requires HTTPS (or localhost for development). User must grant permission - request shows browser prompt. Accuracy varies by device (GPS, WiFi, IP). enableHighAccuracy uses GPS on mobile (slower, more battery). Desktop typically uses IP/WiFi regardless.
Warning:watchPosition can drain battery - always clearWatch() when done. Don't rely on optional fields (altitude, speed, heading) - may be null. Location can be spoofed - don't trust for security. Privacy concern - explain why you need location to users.
Note: iOS 13+ requires user permission and user gesture to access orientation/motion. Request permission with DeviceOrientationEvent.requestPermission(). Events fire frequently (30-60 times/second) - throttle expensive operations. Desktop browsers may not support these events.
Warning: Sensor data can be noisy - apply smoothing/filtering for better results. Battery drain with continuous monitoring - remove listeners when not needed. Privacy concern - can be used for fingerprinting. Not all devices have all sensors (gyroscope, magnetometer).
6.3 Battery Status API (deprecated but legacy)
Property
Type
Description
Status
navigator.getBattery()
Promise<BatteryManager>
Returns Promise with battery status object.
DEPRECATED
level
number
Battery level 0.0 to 1.0 (0% to 100%).
Read-only
charging
boolean
true if device is charging.
Read-only
chargingTime
number
Seconds until fully charged. Infinity if not charging or unknown.
Read-only
dischargingTime
number
Seconds until battery empty. Infinity if charging or unknown.
Read-only
Event
When Fired
levelchange
Battery level changed
chargingchange
Charging status changed (plugged/unplugged)
chargingtimechange
Charging time estimate changed
dischargingtimechange
Discharging time estimate changed
Example: Battery status monitoring
// Check if API is availableif ("getBattery" in navigator) { navigator.getBattery().then((battery) => { // Initial state console.log(`Battery level: ${battery.level * 100}%`); console.log(`Charging: ${battery.charging}`); console.log(`Charging time: ${battery.chargingTime} seconds`); console.log(`Discharging time: ${battery.dischargingTime} seconds`); // Listen for level changes battery.addEventListener("levelchange", () => { console.log(`Battery level: ${battery.level * 100}%`); // Warn if low battery if (battery.level < 0.2 && !battery.charging) { console.warn("Low battery!"); } }); // Listen for charging state changes battery.addEventListener("chargingchange", () => { if (battery.charging) { console.log("Device is charging"); } else { console.log("Device is not charging"); } }); // Charging time updates battery.addEventListener("chargingtimechange", () => { if (battery.chargingTime !== Infinity) { const minutes = Math.floor(battery.chargingTime / 60); console.log(`Fully charged in ${minutes} minutes`); } }); // Discharging time updates battery.addEventListener("dischargingtimechange", () => { if (battery.dischargingTime !== Infinity) { const minutes = Math.floor(battery.dischargingTime / 60); console.log(`Battery empty in ${minutes} minutes`); } }); });} else { console.log("Battery Status API not supported");}
Warning: Battery Status API is DEPRECATED due to privacy concerns (fingerprinting). Removed from most browsers. Firefox disabled by default. Chrome plans removal. Don't rely on this API for new projects. Use feature detection before accessing.
6.4 Vibration API for Haptic Feedback
Method
Syntax
Description
Browser Support
vibrate
navigator.vibrate(pattern)
Vibrates device with pattern. Pattern is number (ms) or array [vibrate, pause, ...].
Note: Vibration only works on mobile devices with vibration hardware. Desktop browsers ignore vibrate() calls. Maximum vibration time limited by browsers (few seconds). User can disable vibration in system settings. Requires user gesture on some browsers.
Warning: Don't overuse vibration - annoying and drains battery. Respect user preferences - provide option to disable. Some users rely on vibration for accessibility - use meaningfully. Vibration patterns limited to prevent abuse (max length, max duration).
6.5 Screen Orientation API and Lock
Property/Method
Description
Browser Support
screen.orientation.type
Current orientation: "portrait-primary", "portrait-secondary", "landscape-primary", "landscape-secondary".
Modern Browsers
screen.orientation.angle
Orientation angle: 0, 90, 180, or 270 degrees.
Modern Browsers
screen.orientation.lock(orientation)
Locks to specific orientation. Returns Promise. Requires fullscreen on most browsers.
Modern Mobile
screen.orientation.unlock()
Unlocks orientation, allowing device rotation.
Modern Mobile
orientationchange event
Fires when screen orientation changes.
Modern Browsers
Orientation Value
Description
portrait-primary
Normal portrait (0° or 180°)
portrait-secondary
Upside-down portrait (180° or 0°)
landscape-primary
Normal landscape (90° or 270°)
landscape-secondary
Reverse landscape (270° or 90°)
portrait
Any portrait orientation
landscape
Any landscape orientation
any
Allow all orientations (default)
Example: Detect and lock orientation
// Check current orientationif (screen.orientation) { console.log(`Type: ${screen.orientation.type}`); console.log(`Angle: ${screen.orientation.angle}°`);} else { console.log("Screen Orientation API not supported");}// Listen for orientation changesscreen.orientation?.addEventListener("change", () => { console.log(`Orientation changed to: ${screen.orientation.type}`); console.log(`Angle: ${screen.orientation.angle}°`); // Adjust layout based on orientation if (screen.orientation.type.startsWith("landscape")) { document.body.classList.add("landscape"); document.body.classList.remove("portrait"); } else { document.body.classList.add("portrait"); document.body.classList.remove("landscape"); }});// Lock orientation (requires fullscreen)async function lockLandscape() { try { // Enter fullscreen first await document.documentElement.requestFullscreen(); // Lock to landscape await screen.orientation.lock("landscape"); console.log("Locked to landscape"); } catch (error) { console.error("Failed to lock orientation:", error); }}// Lock to specific orientationasync function lockOrientation(orientation) { try { await screen.orientation.lock(orientation); console.log(`Locked to ${orientation}`); } catch (error) { if (error.name === "NotSupportedError") { console.error("Orientation lock not supported"); } else if (error.name === "SecurityError") { console.error("Must be in fullscreen to lock orientation"); } else { console.error("Lock failed:", error); } }}// Unlock orientationfunction unlockOrientation() { screen.orientation?.unlock(); console.log("Orientation unlocked");}// Game example: lock to landscape when playingasync function startGame() { await document.documentElement.requestFullscreen(); await screen.orientation.lock("landscape"); // Start game}function exitGame() { screen.orientation.unlock(); document.exitFullscreen();}
Note: Orientation lock typically requires fullscreen mode. Desktop browsers may not support orientation lock. Use CSS media queries for responsive design alongside API. Some browsers use window.orientation (deprecated) - use screen.orientation instead.
Warning: Don't lock orientation unless necessary (games, video). Respect user preference - they rotated device intentionally. Unlock orientation when leaving fullscreen. May not work in iframes or certain contexts.
6.6 Generic Sensor API and Accelerometer
Sensor
Description
Browser Support
Accelerometer
Measures acceleration in m/s² along X, Y, Z axes.
Limited (Chrome)
LinearAccelerationSensor
Acceleration excluding gravity.
Limited (Chrome)
GravitySensor
Gravity component of acceleration.
Limited (Chrome)
Gyroscope
Measures angular velocity (rotation rate) in rad/s.
Limited (Chrome)
Magnetometer
Measures magnetic field in µT (microtesla).
Limited (Chrome)
AbsoluteOrientationSensor
Device orientation relative to Earth's coordinate system.
Limited (Chrome)
RelativeOrientationSensor
Device orientation relative to arbitrary reference.
Limited (Chrome)
AmbientLightSensor
Measures ambient light in lux.
Limited (Chrome)
Sensor Method/Property
Description
start()
Starts sensor. Fires reading event when data available.
// Gyroscope (rotation rate)try { const gyroscope = new Gyroscope({ "frequency": 60 }); gyroscope.addEventListener("reading", () => { console.log(`Rotation rate: x=${gyroscope.x}, y=${gyroscope.y}, z=${gyroscope.z} rad/s`); // Convert to degrees per second const degPerSec = { "x": gyroscope.x * 180 / Math.PI, "y": gyroscope.y * 180 / Math.PI, "z": gyroscope.z * 180 / Math.PI }; console.log(`Rotation (deg/s): x=${degPerSec.x.toFixed(2)}, y=${degPerSec.y.toFixed(2)}, z=${degPerSec.z.toFixed(2)}`); }); gyroscope.start();} catch (error) { console.error("Gyroscope not supported:", error);}// Absolute orientation (quaternion)try { const orientation = new AbsoluteOrientationSensor({ "frequency": 60 }); orientation.addEventListener("reading", () => { // Quaternion representation const [x, y, z, w] = orientation.quaternion; console.log(`Quaternion: [${x}, ${y}, ${z}, ${w}]`); // Convert to Euler angles const angles = quaternionToEuler(x, y, z, w); console.log(`Euler angles: ${JSON.stringify(angles)}`); }); orientation.start();} catch (error) { console.error("AbsoluteOrientationSensor not supported:", error);}// Helper: Convert quaternion to Euler anglesfunction quaternionToEuler(x, y, z, w) { const roll = Math.atan2(2 * (w * x + y * z), 1 - 2 * (x * x + y * y)); const pitch = Math.asin(2 * (w * y - z * x)); const yaw = Math.atan2(2 * (w * z + x * y), 1 - 2 * (y * y + z * z)); return { "roll": roll * 180 / Math.PI, "pitch": pitch * 180 / Math.PI, "yaw": yaw * 180 / Math.PI };}// Ambient light sensortry { const lightSensor = new AmbientLightSensor({ "frequency": 1 }); lightSensor.addEventListener("reading", () => { console.log(`Illuminance: ${lightSensor.illuminance} lux`); // Adjust UI based on ambient light if (lightSensor.illuminance < 50) { document.body.classList.add("dark-mode"); } else { document.body.classList.remove("dark-mode"); } }); lightSensor.start();} catch (error) { console.error("AmbientLightSensor not supported:", error);}
Note: Generic Sensor API has limited browser support (mainly Chrome). Requires HTTPS and user permission. Sensors may not be available on all devices. Use frequency option to control sampling rate (affects battery and CPU). Always wrap in try/catch for feature detection.
Warning: Sensor APIs can drain battery with high frequency - use lowest frequency needed. Always stop() sensors when not in use. Privacy concern - sensor data can be used for fingerprinting or side-channel attacks. Check browser compatibility before using - very limited support.
Device and Sensor API Best Practices
Always check feature availability with if ("geolocation" in navigator) before using APIs
Request location/sensor permissions with clear explanation why you need them
Use enableHighAccuracy: false for geolocation unless GPS precision is critical
Always clearWatch() geolocation watches when done to save battery
Throttle or debounce orientation/motion event handlers - they fire very frequently
Request DeviceOrientation/Motion permissions on iOS 13+ with user gesture
Don't rely on Battery Status API - deprecated and removed from most browsers
Use vibration sparingly - annoying when overused and drains battery
Orientation lock requires fullscreen mode - unlock when exiting fullscreen
Generic Sensor API has limited support - use DeviceOrientation/Motion for broader compatibility
Always stop sensors when not needed to conserve battery and CPU
Handle permission denials gracefully - provide fallback UX
7. File and Clipboard APIs
7.1 File API for File Object Manipulation
Property
Type
Description
Read-Only
name
string
File name with extension (e.g., "photo.jpg"). Does not include path.
Yes
size
number
File size in bytes.
Yes
type
string
MIME type (e.g., "image/jpeg", "text/plain"). Empty string if unknown.
Yes
lastModified
number
Last modified timestamp in milliseconds since epoch.
Yes
lastModifiedDate
Date
Last modified date as Date object. Deprecated - use lastModified instead.
Yes
Method
Returns
Description
slice(start, end, contentType)
Blob
Returns portion of file as new Blob. Parameters optional. Useful for chunked uploads.
text()
Promise<string>
Reads file content as text. Modern alternative to FileReader.
arrayBuffer()
Promise<ArrayBuffer>
Reads file content as ArrayBuffer. Modern alternative to FileReader.
stream()
ReadableStream
Returns ReadableStream for streaming file content.
// Read file as textasync function readFileAsText(file) { try { const text = await file.text(); console.log("File content:", text); return text; } catch (error) { console.error("Error reading file:", error); }}// Read file as ArrayBufferasync function readFileAsArrayBuffer(file) { try { const buffer = await file.arrayBuffer(); console.log("Buffer size:", buffer.byteLength); return buffer; } catch (error) { console.error("Error reading file:", error); }}// Read file as data URL for previewasync function readFileAsDataURL(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = () => reject(reader.error); reader.readAsDataURL(file); });}// UsagefileInput.addEventListener("change", async (event) => { const file = event.target.files[0]; if (file.type.startsWith("text/")) { const text = await file.text(); console.log(text); } else if (file.type.startsWith("image/")) { const dataURL = await readFileAsDataURL(file); const img = document.createElement("img"); img.src = dataURL; document.body.appendChild(img); }});// Slice file for chunked uploadfunction uploadFileInChunks(file, chunkSize = 1024 * 1024) { const chunks = Math.ceil(file.size / chunkSize); for (let i = 0; i < chunks; i++) { const start = i * chunkSize; const end = Math.min(start + chunkSize, file.size); const chunk = file.slice(start, end); console.log(`Chunk ${i + 1}/${chunks}: ${chunk.size} bytes`); uploadChunk(chunk, i); }}async function uploadChunk(chunk, index) { const formData = new FormData(); formData.append("chunk", chunk); formData.append("index", index); await fetch("/upload-chunk", { "method": "POST", "body": formData });}
Note: File objects are Blob subclasses with additional name and lastModified properties. Files from <input type="file"> are read-only. Use accept attribute to filter file types in picker. Modern methods (text(), arrayBuffer()) are Promise-based and simpler than FileReader.
7.2 FileReader API for File Content Reading
Method
Description
Result Format
readAsText(blob, encoding)
Reads file as text string. Optional encoding (default UTF-8).
string
readAsDataURL(blob)
Reads file as data URL (base64-encoded). Good for images.
data:image/jpeg;base64,...
readAsArrayBuffer(blob)
Reads file as ArrayBuffer. Good for binary data.
ArrayBuffer
readAsBinaryString(blob)
Reads file as binary string. Deprecated - use readAsArrayBuffer instead.
Note: FileReader is event-based and asynchronous. For modern code, prefer file.text() or file.arrayBuffer() which return Promises. readAsDataURL creates large base64 strings - consider object URLs for better performance. Each FileReader instance can only read one file at a time.
7.3 Clipboard API for Copy/Paste Operations
Method
Description
Browser Support
navigator.clipboard.writeText(text)
Writes text to clipboard. Returns Promise. Requires user gesture or permission.
Modern Browsers
navigator.clipboard.readText()
Reads text from clipboard. Returns Promise<string>. Requires permission.
Modern Browsers
navigator.clipboard.write(items)
Writes ClipboardItem array to clipboard. Supports images, rich content.
Modern Browsers
navigator.clipboard.read()
Reads ClipboardItem array from clipboard. Returns Promise<ClipboardItem[]>.
Modern Browsers
Legacy Method
Description
Status
document.execCommand("copy")
Copies selected text to clipboard. Deprecated but widely supported.
DEPRECATED
document.execCommand("cut")
Cuts selected text to clipboard. Deprecated but widely supported.
DEPRECATED
document.execCommand("paste")
Pastes from clipboard. Deprecated and security restricted.
DEPRECATED
Example: Modern clipboard API
// Copy text to clipboardasync function copyToClipboard(text) { try { await navigator.clipboard.writeText(text); console.log("Text copied to clipboard"); return true; } catch (error) { console.error("Failed to copy:", error); return false; }}// Read text from clipboardasync function readFromClipboard() { try { const text = await navigator.clipboard.readText(); console.log("Clipboard content:", text); return text; } catch (error) { console.error("Failed to read clipboard:", error); return null; }}// Copy button handlerdocument.querySelector(".copy-btn").addEventListener("click", async () => { const text = document.querySelector(".code-block").textContent; const success = await copyToClipboard(text); if (success) { // Show feedback showToast("Copied to clipboard!"); }});// Paste handlerdocument.querySelector(".paste-btn").addEventListener("click", async () => { const text = await readFromClipboard(); if (text) { document.querySelector("textarea").value = text; }});// Copy rich content (HTML, images)async function copyRichContent() { const blob = new Blob( ["<h1>Hello</h1><p>Rich content</p>"], { "type": "text/html" } ); const item = new ClipboardItem({ "text/html": blob }); try { await navigator.clipboard.write([item]); console.log("Rich content copied"); } catch (error) { console.error("Failed to copy rich content:", error); }}// Copy image to clipboardasync function copyImageToClipboard(imageUrl) { try { const response = await fetch(imageUrl); const blob = await response.blob(); const item = new ClipboardItem({ [blob.type]: blob }); await navigator.clipboard.write([item]); console.log("Image copied to clipboard"); } catch (error) { console.error("Failed to copy image:", error); }}// Read clipboard with multiple formatsasync function readClipboardData() { try { const items = await navigator.clipboard.read(); for (const item of items) { console.log("Clipboard item types:", item.types); for (const type of item.types) { const blob = await item.getType(type); if (type.startsWith("text/")) { const text = await blob.text(); console.log(`${type}:`, text); } else if (type.startsWith("image/")) { const url = URL.createObjectURL(blob); console.log(`${type}:`, url); const img = document.createElement("img"); img.src = url; document.body.appendChild(img); } } } } catch (error) { console.error("Failed to read clipboard:", error); }}
Example: Legacy execCommand fallback
// Copy with fallbackasync function copyText(text) { // Try modern API if (navigator.clipboard && navigator.clipboard.writeText) { try { await navigator.clipboard.writeText(text); return true; } catch (error) { console.warn("Clipboard API failed, trying fallback"); } } // Fallback to execCommand const textarea = document.createElement("textarea"); textarea.value = text; textarea.style.position = "fixed"; textarea.style.opacity = "0"; document.body.appendChild(textarea); textarea.select(); let success = false; try { success = document.execCommand("copy"); } catch (error) { console.error("execCommand failed:", error); } document.body.removeChild(textarea); return success;}// Copy from elementfunction copyElementText(element) { const text = element.textContent || element.innerText; return copyText(text);}// Select and copyfunction selectAndCopy(element) { const range = document.createRange(); range.selectNodeContents(element); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); const success = document.execCommand("copy"); selection.removeAllRanges(); return success;}
Note: Clipboard API requires HTTPS (or localhost). writeText() requires user gesture on some browsers. readText() requires clipboard permission. ClipboardItem supports multiple MIME types. Always provide fallback for older browsers.
Warning: Reading clipboard requires user permission - prompt may appear. Don't spam clipboard writes - annoying UX. execCommand is deprecated but still needed for fallback. Never auto-read clipboard on page load - privacy violation.
7.4 Drag and Drop API with DataTransfer
Event
Target
When Fired
Cancelable
dragstart
Source element
User starts dragging element
Yes
drag
Source element
While dragging (fires continuously)
Yes
dragend
Source element
Drag operation ends (drop or cancel)
No
dragenter
Drop target
Dragged element enters drop target
Yes
dragover
Drop target
Dragged element over drop target (fires continuously)
Yes
dragleave
Drop target
Dragged element leaves drop target
No
drop
Drop target
Element dropped on target. Must preventDefault() on dragover.
Yes
DataTransfer Property
Type
Description
dropEffect
string
Visual feedback: "none", "copy", "move", "link". Set in dragover.
effectAllowed
string
Allowed operations: "none", "copy", "move", "link", "copyMove", "all", etc. Set in dragstart.
files
FileList
Files being dragged (from file system). Available in drop event.
items
DataTransferItemList
List of drag data items. More powerful than files.
types
string[]
Array of data format types available.
DataTransfer Method
Description
setData(format, data)
Sets drag data. Common formats: "text/plain", "text/html", "text/uri-list".
getData(format)
Gets drag data. Only accessible in drop event.
clearData(format)
Clears drag data. Optional format parameter.
setDragImage(element, x, y)
Sets custom drag preview image. x, y are hotspot offsets.
Example: Basic drag and drop
// Make element draggableconst draggable = document.querySelector(".draggable");draggable.draggable = true;// Drag startdraggable.addEventListener("dragstart", (event) => { console.log("Drag started"); // Set data event.dataTransfer.setData("text/plain", event.target.id); event.dataTransfer.setData("text/html", event.target.outerHTML); // Set effect event.dataTransfer.effectAllowed = "move"; // Visual feedback event.target.style.opacity = "0.5";});// Drag enddraggable.addEventListener("dragend", (event) => { console.log("Drag ended"); event.target.style.opacity = "1";});// Drop targetconst dropZone = document.querySelector(".drop-zone");// Prevent default to allow dropdropZone.addEventListener("dragover", (event) => { event.preventDefault(); event.dataTransfer.dropEffect = "move";});// Visual feedbackdropZone.addEventListener("dragenter", (event) => { event.preventDefault(); dropZone.classList.add("drag-over");});dropZone.addEventListener("dragleave", () => { dropZone.classList.remove("drag-over");});// Handle dropdropZone.addEventListener("drop", (event) => { event.preventDefault(); dropZone.classList.remove("drag-over"); // Get data const id = event.dataTransfer.getData("text/plain"); console.log("Dropped element ID:", id); // Move element const element = document.getElementById(id); dropZone.appendChild(element);});
Example: File drag and drop
const dropZone = document.getElementById("file-drop-zone");// Prevent default browser behavior["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => { dropZone.addEventListener(eventName, (e) => { e.preventDefault(); e.stopPropagation(); });});// Visual feedback["dragenter", "dragover"].forEach((eventName) => { dropZone.addEventListener(eventName, () => { dropZone.classList.add("highlight"); });});["dragleave", "drop"].forEach((eventName) => { dropZone.addEventListener(eventName, () => { dropZone.classList.remove("highlight"); });});// Handle dropped filesdropZone.addEventListener("drop", (event) => { const files = event.dataTransfer.files; console.log(`${files.length} file(s) dropped`); // Process files Array.from(files).forEach((file) => { console.log("File:", file.name, file.type, file.size); handleFile(file); }); // Or use DataTransferItemList for more control const items = event.dataTransfer.items; for (let i = 0; i < items.length; i++) { const item = items[i]; if (item.kind === "file") { const file = item.getAsFile(); console.log("File from item:", file.name); } }});// Validate dropped filesfunction validateDrop(event) { const items = event.dataTransfer.items; for (let i = 0; i < items.length; i++) { const item = items[i]; // Check if file if (item.kind !== "file") { return false; } // Check file type if (!item.type.startsWith("image/")) { alert("Only images allowed"); return false; } } return true;}// Preview dropped imagesasync function handleFile(file) { if (!file.type.startsWith("image/")) return; const img = document.createElement("img"); img.file = file; const reader = new FileReader(); reader.onload = (e) => { img.src = e.target.result; img.style.maxWidth = "200px"; dropZone.appendChild(img); }; reader.readAsDataURL(file);}
Note: Must call preventDefault() on dragover event to allow drop. getData() only works in drop event (security). Set draggable="true" attribute on elements to make draggable. Use event.dataTransfer.files for dropped files.
Warning:dragover fires very frequently - throttle expensive operations. Some browsers restrict file access from drag/drop for security. Always validate dropped files before processing. Mobile browsers have limited drag/drop support.
7.5 File System Access API for Local Files
Method
Description
Browser Support
window.showOpenFilePicker(options)
Shows file picker. Returns Promise<FileSystemFileHandle[]>. Requires user gesture.
Modern Browsers
window.showSaveFilePicker(options)
Shows save dialog. Returns Promise<FileSystemFileHandle>. Requires user gesture.
Modern Browsers
window.showDirectoryPicker(options)
Shows directory picker. Returns Promise<FileSystemDirectoryHandle>. Requires user gesture.
Modern Browsers
FileSystemFileHandle Method
Description
getFile()
Returns Promise<File> with file contents.
createWritable()
Returns Promise<FileSystemWritableFileStream> for writing.
queryPermission(descriptor)
Checks permission status. Returns "granted", "denied", or "prompt".
requestPermission(descriptor)
Requests permission. Returns "granted" or "denied".
Note: File System Access API requires user gesture (click event). Only works in secure contexts (HTTPS). Browser shows permission prompt for each file access. File handles can be persisted in IndexedDB for later use. Very limited browser support (mainly Chrome).
7.6 Directory Handle and File Handle APIs
FileSystemDirectoryHandle Method
Description
getFileHandle(name, options)
Gets file in directory. Options: create: true to create if missing.
getDirectoryHandle(name, options)
Gets subdirectory. Options: create: true to create if missing.
removeEntry(name, options)
Removes file/directory. Options: recursive: true for directories.
values()
Returns async iterator of entries in directory.
keys()
Returns async iterator of entry names.
entries()
Returns async iterator of [name, handle] pairs.
Example: Directory operations
// Pick directoryasync function pickDirectory() { try { const dirHandle = await window.showDirectoryPicker(); console.log("Directory:", dirHandle.name); // List contents await listDirectory(dirHandle); return dirHandle; } catch (error) { console.error("Error picking directory:", error); }}// List directory contentsasync function listDirectory(dirHandle) { console.log(`Contents of ${dirHandle.name}:`); for await (const entry of dirHandle.values()) { console.log(` ${entry.kind}: ${entry.name}`); if (entry.kind === "file") { const file = await entry.getFile(); console.log(` Size: ${file.size} bytes`); } }}// Read all files in directoryasync function readAllFiles(dirHandle) { const files = []; for await (const entry of dirHandle.values()) { if (entry.kind === "file") { const file = await entry.getFile(); const content = await file.text(); files.push({ name: file.name, content }); } } return files;}// Create file in directoryasync function createFileInDirectory(dirHandle, fileName, content) { try { const fileHandle = await dirHandle.getFileHandle(fileName, { "create": true }); const writable = await fileHandle.createWritable(); await writable.write(content); await writable.close(); console.log(`Created: ${fileName}`); } catch (error) { console.error("Error creating file:", error); }}// Create subdirectoryasync function createSubdirectory(dirHandle, dirName) { try { const subDirHandle = await dirHandle.getDirectoryHandle(dirName, { "create": true }); console.log(`Created directory: ${dirName}`); return subDirHandle; } catch (error) { console.error("Error creating directory:", error); }}// Delete fileasync function deleteFile(dirHandle, fileName) { try { await dirHandle.removeEntry(fileName); console.log(`Deleted: ${fileName}`); } catch (error) { console.error("Error deleting file:", error); }}// Recursively list directory treeasync function listDirectoryTree(dirHandle, indent = "") { for await (const [name, handle] of dirHandle.entries()) { console.log(`${indent}${handle.kind === "directory" ? "📁" : "📄"} ${name}`); if (handle.kind === "directory") { await listDirectoryTree(handle, indent + " "); } }}// Search for filesasync function findFiles(dirHandle, pattern) { const results = []; for await (const entry of dirHandle.values()) { if (entry.kind === "file" && entry.name.includes(pattern)) { results.push(entry); } else if (entry.kind === "directory") { const subResults = await findFiles(entry, pattern); results.push(...subResults); } } return results;}
Warning: File System Access API has very limited browser support (mainly Chrome/Edge). Always check if ("showOpenFilePicker" in window). Requires user permission for each directory access. Writing files requires explicit user consent. Not available in iframes or insecure contexts.
File and Clipboard API Best Practices
Validate file type and size before processing - check file.type and file.size
Use modern file.text() and file.arrayBuffer() instead of FileReader when possible
Use object URLs (URL.createObjectURL) instead of data URLs for better performance
Always revoke object URLs with URL.revokeObjectURL() when done to free memory
For clipboard, provide fallback to execCommand for older browser support
Request clipboard permission with clear explanation - users are wary of clipboard access
For drag and drop, always preventDefault() on dragover to enable drop
Registers service worker. Returns Promise<ServiceWorkerRegistration>. Requires HTTPS.
All Modern Browsers
getRegistration
navigator.serviceWorker.getRegistration(scope)
Gets existing registration for scope. Returns Promise<ServiceWorkerRegistration>.
All Modern Browsers
getRegistrations
navigator.serviceWorker.getRegistrations()
Gets all registrations. Returns Promise<ServiceWorkerRegistration[]>.
All Modern Browsers
Lifecycle Event
When Fired
Use Case
install
Service worker first installed. Runs only once per version.
Cache static assets, setup
activate
Service worker activated. After install or on page reload for updated worker.
Clean old caches, claim clients
fetch
Network request intercepted. Fires for all page requests.
Cache strategies, offline support
message
Message received from client (page/worker).
Client-worker communication
sync
Background sync triggered (when online).
Retry failed requests
push
Push notification received.
Show notifications
ServiceWorkerRegistration Property
Type
Description
installing
ServiceWorker
Service worker currently installing. null if none.
waiting
ServiceWorker
Service worker installed, waiting to activate. null if none.
active
ServiceWorker
Active service worker controlling pages. null if none.
scope
string
URL scope of service worker (e.g., "/app/").
updateViaCache
string
Cache mode for updates: "imports", "all", "none".
Example: Register service worker
// Check supportif ("serviceWorker" in navigator) { console.log("Service Workers supported");} else { console.log("Service Workers not supported");}// Register service workerasync function registerServiceWorker() { try { const registration = await navigator.serviceWorker.register("/sw.js", { "scope": "/" // Default is script location }); console.log("Service Worker registered:", registration.scope); // Check state if (registration.installing) { console.log("Service Worker installing"); } else if (registration.waiting) { console.log("Service Worker waiting"); } else if (registration.active) { console.log("Service Worker active"); } return registration; } catch (error) { console.error("Service Worker registration failed:", error); }}// Register on page loadwindow.addEventListener("load", () => { registerServiceWorker();});// Listen for updatesnavigator.serviceWorker.addEventListener("controllerchange", () => { console.log("Service Worker controller changed"); // New service worker took control window.location.reload();});// Check for updates manuallyasync function checkForUpdates() { const registration = await navigator.serviceWorker.getRegistration(); if (registration) { await registration.update(); console.log("Checked for updates"); }}// Unregister service workerasync function unregisterServiceWorker() { const registration = await navigator.serviceWorker.getRegistration(); if (registration) { const success = await registration.unregister(); console.log("Unregistered:", success); }}
Example: Service worker lifecycle (sw.js)
const CACHE_NAME = "my-app-v1";const STATIC_ASSETS = [ "/", "/index.html", "/styles.css", "/app.js", "/logo.png"];// Install event - cache static assetsself.addEventListener("install", (event) => { console.log("Service Worker installing"); event.waitUntil( caches.open(CACHE_NAME).then((cache) => { console.log("Caching static assets"); return cache.addAll(STATIC_ASSETS); }).then(() => { // Skip waiting to activate immediately return self.skipWaiting(); }) );});// Activate event - clean old cachesself.addEventListener("activate", (event) => { console.log("Service Worker activating"); event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames .filter((name) => name !== CACHE_NAME) .map((name) => { console.log("Deleting old cache:", name); return caches.delete(name); }) ); }).then(() => { // Take control of all clients immediately return self.clients.claim(); }) );});// Fetch event - serve from cache or networkself.addEventListener("fetch", (event) => { event.respondWith( caches.match(event.request).then((response) => { // Cache hit - return cached response if (response) { return response; } // Cache miss - fetch from network return fetch(event.request).then((response) => { // Don't cache non-GET requests or non-ok responses if (event.request.method !== "GET" || !response.ok) { return response; } // Clone response (can only read once) const responseToCache = response.clone(); // Cache for next time caches.open(CACHE_NAME).then((cache) => { cache.put(event.request, responseToCache); }); return response; }); }).catch(() => { // Network failed - return offline page return caches.match("/offline.html"); }) );});
Note: Service Workers require HTTPS (localhost OK for development). Only one service worker active per scope. Use skipWaiting() to activate immediately, clients.claim() to control existing pages. Updates check every 24 hours or on navigation. Use versioned cache names to manage updates.
Warning: Service worker has no DOM access - can't manipulate page directly. Runs in separate thread. Use event.waitUntil() to extend event lifetime for async operations. Service worker can be terminated anytime - don't rely on global state. Test offline behavior thoroughly.
8.2 Service Worker Message Passing and Communication
Gets all client windows/tabs. Options: includeUncontrolled, type.
Broadcast to all tabs
clients.get(id)
Gets specific client by ID. Returns Promise<Client>.
Reply to specific tab
clients.openWindow(url)
Opens new window/tab. Returns Promise<WindowClient>. Requires user interaction.
Open notification click
clients.claim()
Makes service worker control all clients immediately (without reload).
Take control on activate
Example: Page to service worker communication
// From page to service workerif (navigator.serviceWorker.controller) { // Send message navigator.serviceWorker.controller.postMessage({ "type": "CACHE_URLS", "urls": ["/page1.html", "/page2.html"] }); console.log("Message sent to service worker");} else { console.log("No active service worker");}// Listen for messages from service workernavigator.serviceWorker.addEventListener("message", (event) => { console.log("Message from service worker:", event.data); if (event.data.type === "CACHE_UPDATED") { console.log("Cache updated:", event.data.urls); } else if (event.data.type === "NEW_VERSION") { showUpdatePrompt(); }});// Request-response pattern with message channelfunction sendMessageWithResponse(message) { return new Promise((resolve, reject) => { const messageChannel = new MessageChannel(); messageChannel.port1.onmessage = (event) => { if (event.data.error) { reject(event.data.error); } else { resolve(event.data); } }; navigator.serviceWorker.controller.postMessage( message, [messageChannel.port2] ); });}// Usageasync function getCacheInfo() { try { const response = await sendMessageWithResponse({ "type": "GET_CACHE_INFO" }); console.log("Cache info:", response); } catch (error) { console.error("Error:", error); }}
Example: Service worker to page communication
// In service worker (sw.js)// Listen for messages from pagesself.addEventListener("message", (event) => { console.log("Message received:", event.data); if (event.data.type === "CACHE_URLS") { // Cache requested URLs cacheUrls(event.data.urls).then(() => { // Reply to sender event.source.postMessage({ "type": "CACHE_UPDATED", "urls": event.data.urls }); }); } else if (event.data.type === "GET_CACHE_INFO") { // Handle request-response with message channel caches.keys().then((cacheNames) => { event.ports[0].postMessage({ "caches": cacheNames, "count": cacheNames.length }); }); } else if (event.data.type === "SKIP_WAITING") { self.skipWaiting(); }});// Broadcast to all clientsasync function notifyAllClients(message) { const clients = await self.clients.matchAll({ "includeUncontrolled": true, "type": "window" }); clients.forEach((client) => { client.postMessage(message); });}// Notify when cache updatedasync function cacheUrls(urls) { const cache = await caches.open(CACHE_NAME); await cache.addAll(urls); // Notify all tabs await notifyAllClients({ "type": "CACHE_UPDATED", "urls": urls });}// Notify specific clientasync function notifyClient(clientId, message) { const client = await self.clients.get(clientId); if (client) { client.postMessage(message); }}// Open window on notification clickself.addEventListener("notificationclick", (event) => { event.notification.close(); event.waitUntil( clients.openWindow("/notifications") );});
Note:postMessage only works if service worker is active and controlling page. Check navigator.serviceWorker.controller before sending. Messages are structured clones - can't send functions, DOM nodes. Use MessageChannel for request-response patterns.
8.3 Background Sync and Periodic Sync APIs
API
Method
Description
Browser Support
Background Sync
registration.sync.register(tag)
Registers one-time sync when online. Returns Promise.
Chrome, Edge
Periodic Sync
registration.periodicSync.register(tag, options)
Registers periodic sync. Options: minInterval in ms. Requires installed PWA.
Note: Background Sync has limited browser support (Chrome, Edge). Periodic Sync requires installed PWA and permission. Browser controls actual sync timing - minInterval is minimum, not guaranteed. Sync events retry automatically if promise rejects.
Warning: Don't rely on Background Sync for critical operations - not supported everywhere. Periodic Sync can be delayed/skipped by browser to save battery. Always provide fallback (manual refresh button). Test offline scenarios thoroughly.
8.4 Push API and Push Notifications
Method
Description
Browser Support
registration.pushManager.subscribe(options)
Subscribes to push notifications. Returns Promise<PushSubscription>. Requires permission.
Subscription expiration timestamp. null if no expiration.
Example: Subscribe to push notifications
// Request notification permissionasync function requestNotificationPermission() { const permission = await Notification.requestPermission(); console.log("Notification permission:", permission); return permission === "granted";}// Subscribe to pushasync function subscribeToPush() { try { // Check permission if (Notification.permission !== "granted") { const granted = await requestNotificationPermission(); if (!granted) { console.log("Notification permission denied"); return; } } // Get service worker registration const registration = await navigator.serviceWorker.ready; // Check existing subscription let subscription = await registration.pushManager.getSubscription(); if (!subscription) { // Subscribe (need VAPID public key from server) subscription = await registration.pushManager.subscribe({ "userVisibleOnly": true, "applicationServerKey": urlBase64ToUint8Array(VAPID_PUBLIC_KEY) }); console.log("Subscribed to push"); } else { console.log("Already subscribed"); } // Send subscription to server await sendSubscriptionToServer(subscription); return subscription; } catch (error) { console.error("Push subscription failed:", error); }}// Convert VAPID keyfunction urlBase64ToUint8Array(base64String) { const padding = "=".repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/-/g, "+") .replace(/_/g, "/"); const rawData = atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray;}// Send subscription to serverasync function sendSubscriptionToServer(subscription) { const response = await fetch("/api/subscribe", { "method": "POST", "headers": { "Content-Type": "application/json" }, "body": JSON.stringify(subscription) }); if (response.ok) { console.log("Subscription saved on server"); }}// Unsubscribeasync function unsubscribeFromPush() { const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.getSubscription(); if (subscription) { await subscription.unsubscribe(); console.log("Unsubscribed from push"); // Notify server await fetch("/api/unsubscribe", { "method": "POST", "body": JSON.stringify({ "endpoint": subscription.endpoint }) }); }}
Example: Handle push events in service worker
// In service worker - listen for push eventsself.addEventListener("push", (event) => { console.log("Push received"); let data = { "title": "New Notification", "body": "You have a new message", "icon": "/icon.png", "badge": "/badge.png" }; // Parse push data if (event.data) { data = event.data.json(); } // Show notification event.waitUntil( self.registration.showNotification(data.title, { "body": data.body, "icon": data.icon, "badge": data.badge, "data": data.url, "tag": data.tag || "default", "requireInteraction": false, "actions": [ { "action": "open", "title": "Open" }, { "action": "close", "title": "Close" } ] }) );});// Handle notification clickself.addEventListener("notificationclick", (event) => { console.log("Notification clicked:", event.action); event.notification.close(); if (event.action === "open") { // Open URL from notification data event.waitUntil( clients.openWindow(event.notification.data || "/") ); } else if (event.action === "close") { // Just close (default behavior) } else { // Default click (no action button) event.waitUntil( clients.openWindow(event.notification.data || "/") ); }});// Handle notification closeself.addEventListener("notificationclose", (event) => { console.log("Notification closed:", event.notification.tag); // Track analytics fetch("/api/analytics/notification-closed", { "method": "POST", "body": JSON.stringify({ "tag": event.notification.tag, "timestamp": Date.now() }) });});
Note: Push API requires HTTPS and service worker. Need VAPID keys from server for applicationServerKey. Must show notification when push received (userVisibleOnly: true). Subscription can expire - check expirationTime and resubscribe.
Warning: Always request permission with user gesture (button click). Don't spam notifications - users will block. Test notification display on different platforms - varies by OS. Handle subscription expiration and errors gracefully. Safari has different push implementation (APNs).
Note: Background Fetch has very limited support (Chrome only). Good for large file downloads that continue even if page closed. Browser shows download UI to user. Files available after download completes via backgroundfetchsuccess event.
8.6 Workbox Integration Patterns
Workbox Strategy
Description
Use Case
NetworkFirst
Network request first, fallback to cache if offline.
Dynamic content, API responses
CacheFirst
Cache first, fallback to network if missing.
Static assets, fonts, images
StaleWhileRevalidate
Return cache immediately, update cache in background.
Note: Workbox simplifies service worker development with built-in strategies and plugins. Use workbox-cli to generate precache manifest. Workbox handles common patterns: caching, routing, expiration, background sync. Consider bundle size when using Workbox - can use module imports instead of CDN.
Service Worker Best Practices
Always use versioned cache names - easier to manage updates and cleanup
Use skipWaiting() and clients.claim() for immediate activation
Implement proper fetch strategies - CacheFirst for static, NetworkFirst for dynamic
Always include offline fallback page in precache
Test service worker updates - old workers can persist and cause bugs
Use event.waitUntil() to extend event lifetime for async operations
Note: Use performance.now() instead of Date.now() for accurate timing - higher resolution and not affected by system clock changes. Navigation Timing Level 1 (performance.timing) is deprecated but still widely supported. Use getEntriesByType("navigation") for modern apps.
9.2 Resource Timing for Asset Loading Analysis
Method
Description
Returns
performance.getEntriesByType("resource")
Gets all resource timing entries (CSS, JS, images, etc.).
PerformanceResourceTiming[]
performance.getEntriesByName(name, "resource")
Gets resource entries for specific URL.
PerformanceResourceTiming[]
performance.clearResourceTimings()
Clears resource timing buffer.
void
performance.setResourceTimingBufferSize(n)
Sets max number of resource entries (default 250).
void
PerformanceResourceTiming Property
Type
Description
name
string
Resource URL.
initiatorType
string
How resource loaded: "script", "link", "img", "fetch", "xmlhttprequest", etc.
duration
number
Total load time in ms (responseEnd - startTime).
transferSize
number
Size of response including headers (bytes). 0 if cached.
Note:transferSize is 0 for cached resources. For cross-origin resources, need Timing-Allow-Origin header to get detailed timing. Resource buffer has default limit of 250 entries - increase with setResourceTimingBufferSize() if needed.
Warning: Resource timing can consume memory with many resources. Clear periodically with clearResourceTimings() in SPAs. Cross-origin resources show limited timing without CORS headers. Don't rely on exact values - use for relative comparisons and trends.
9.3 User Timing API for Custom Metrics
Method
Description
Use Case
performance.mark(name, options)
Creates named timestamp marker. Options: startTime, detail.
Mark important events
performance.measure(name, options)
Measures duration between marks/events. Options: start, end, duration.
Measure custom metrics
performance.clearMarks(name)
Clears specific or all marks. Omit name to clear all.
Cleanup
performance.clearMeasures(name)
Clears specific or all measures. Omit name to clear all.
Cleanup
performance.getEntriesByType("mark")
Gets all marks. Returns PerformanceMark[].
Retrieve marks
performance.getEntriesByType("measure")
Gets all measures. Returns PerformanceMeasure[].
Retrieve measures
Example: User timing for custom metrics
// Mark eventsperformance.mark("page-start");// Simulate workawait loadData();performance.mark("data-loaded");await renderUI();performance.mark("ui-rendered");await initializeFeatures();performance.mark("features-ready");// Measure durationsperformance.measure("data-load-time", "page-start", "data-loaded");performance.measure("ui-render-time", "data-loaded", "ui-rendered");performance.measure("feature-init-time", "ui-rendered", "features-ready");performance.measure("total-init-time", "page-start", "features-ready");// Get measurementsconst measures = performance.getEntriesByType("measure");console.log("Custom Metrics:");measures.forEach(measure => { console.log(` ${measure.name}: ${measure.duration.toFixed(2)}ms`);});// Measure with navigation timing referenceperformance.measure("time-to-interactive", { "start": 0, // Navigation start "end": "features-ready"});// Measure with explicit durationperformance.measure("render-budget", { "start": "data-loaded", "duration": 16.67 // 60fps budget});// Add custom data to marksperformance.mark("api-call", { "detail": { "endpoint": "/api/users", "method": "GET", "status": 200 }});// Retrieve with detailconst marks = performance.getEntriesByName("api-call", "mark");console.log("API call detail:", marks[0].detail);// Clear old marks/measuresperformance.clearMarks("page-start");performance.clearMeasures("data-load-time");// Clear allperformance.clearMarks();performance.clearMeasures();
Example: Performance monitoring helper
class PerformanceMonitor { constructor() { this.metrics = new Map(); } // Start timing start(name) { const markName = `${name}-start`; performance.mark(markName); this.metrics.set(name, { "startMark": markName }); } // End timing and measure end(name) { const markName = `${name}-end`; performance.mark(markName); const metric = this.metrics.get(name); if (metric) { performance.measure(name, metric.startMark, markName); const [measure] = performance.getEntriesByName(name, "measure"); metric.duration = measure.duration; console.log(`${name}: ${measure.duration.toFixed(2)}ms`); return measure.duration; } } // Get metric get(name) { return this.metrics.get(name); } // Get all metrics getAll() { return Object.fromEntries(this.metrics); } // Report to analytics report() { const measures = performance.getEntriesByType("measure"); measures.forEach(measure => { // Send to analytics service sendAnalytics({ "metric": measure.name, "value": measure.duration, "timestamp": Date.now() }); }); } // Clear all clear() { performance.clearMarks(); performance.clearMeasures(); this.metrics.clear(); }}// Usageconst monitor = new PerformanceMonitor();monitor.start("database-query");const data = await fetchFromDatabase();monitor.end("database-query");monitor.start("render-component");renderComponent(data);monitor.end("render-component");console.log("All metrics:", monitor.getAll());monitor.report();
Note: User Timing API is perfect for custom metrics specific to your app. Integrates with browser DevTools performance timeline. Use descriptive names for marks and measures. Can add custom data with detail option.
9.4 Paint Timing and Largest Contentful Paint
Paint Metric
Description
Good Value
First Paint (FP)
Time when browser first renders anything (background, border, etc.).
< 1s
First Contentful Paint (FCP)
Time when first text/image renders. Core Web Vital.
< 1.8s
Largest Contentful Paint (LCP)
Time when largest content element renders. Core Web Vital.
< 2.5s
First Input Delay (FID)
Time from first interaction to browser response. Core Web Vital.
< 100ms
Cumulative Layout Shift (CLS)
Visual stability - sum of unexpected layout shifts. Core Web Vital.
Note: Core Web Vitals (LCP, FID, CLS) are Google ranking factors. Use PerformanceObserver with buffered: true to get historical entries. LCP can change as page loads - track latest value. Consider using web-vitals library for production.
Warning: Paint timing varies greatly by device and network. Don't compare absolute values across users - track trends. LCP element must be visible in viewport. CLS excludes shifts within 500ms of user input. Test on real devices, not just desktop.
9.5 Long Tasks API for Performance Monitoring
Property
Description
Browser Support
Long Task
Task that blocks main thread for >50ms. Causes jank and poor responsiveness.
Chrome, Edge
entry.duration
Task duration in ms. Long tasks are >50ms.
Chrome, Edge
entry.attribution
Array of task attribution (container, script URL, etc.).
Note: Long Tasks API has limited browser support (Chrome, Edge). Long tasks (>50ms) cause poor interactivity. Total Blocking Time (TBT) is sum of blocking portions of all long tasks. Use to identify performance bottlenecks.
Note: Always use feature detection with PerformanceObserver.supportedEntryTypes. Use buffered: true to get entries that occurred before observe() called. Modern approach uses type (single type), legacy uses entryTypes (array).
Warning: Performance observers can impact performance if callback is expensive. Keep callbacks lightweight. Disconnect observers when done to prevent memory leaks. Don't observe in tight loops. Use debouncing for resource observer in SPAs.
Performance API Best Practices
Use performance.now() for accurate timing - higher resolution than Date.now()
Monitor Core Web Vitals (LCP, FID, CLS) - Google ranking factors
Use PerformanceObserver with buffered: true to capture early events
Always check PerformanceObserver.supportedEntryTypes before observing
User Timing API perfect for custom metrics - use descriptive mark/measure names
Track Resource Timing to identify slow or large assets
Monitor long tasks (>50ms) to identify responsiveness issues
Send performance data to analytics - track trends over time, not absolute values
Clear marks/measures periodically in SPAs to prevent memory growth
Report metrics on page hide/unload - use visibilitychange event
Test performance on real devices and networks - desktop not representative
Consider using web-vitals library for production Web Vitals tracking
10. Intersection and Resize Observer APIs
10.1 Intersection Observer for Visibility Detection
Ratio of intersection (0.0 to 1.0). 1.0 = fully visible.
intersectionRect
DOMRectReadOnly
Bounding rect of visible portion of element.
boundingClientRect
DOMRectReadOnly
Bounding rect of entire element.
rootBounds
DOMRectReadOnly
Bounding rect of root element (or viewport if root is null).
time
number
Timestamp when intersection change occurred.
Example: Basic intersection observer
// Create intersection observerconst observer = new IntersectionObserver((entries) => { entries.forEach(entry => { console.log("Element:", entry.target); console.log("Is intersecting:", entry.isIntersecting); console.log("Intersection ratio:", entry.intersectionRatio); if (entry.isIntersecting) { // Element entered viewport entry.target.classList.add("visible"); console.log("Element became visible"); } else { // Element left viewport entry.target.classList.remove("visible"); console.log("Element became hidden"); } });});// Observe elementsconst elements = document.querySelectorAll(".observe-me");elements.forEach(el => observer.observe(el));// Unobserve specific elementobserver.unobserve(elements[0]);// Disconnect allobserver.disconnect();// One-time visibility checkconst oneTimeObserver = new IntersectionObserver((entries, obs) => { entries.forEach(entry => { if (entry.isIntersecting) { // Do something once console.log("Element appeared!"); // Stop observing after first intersection obs.unobserve(entry.target); } });});const heroElement = document.querySelector(".hero");oneTimeObserver.observe(heroElement);
Example: Trigger animations on visibility
// Animate elements when they enter viewportconst animateObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { // Add animation class entry.target.classList.add("animate-in"); // Optional: stop observing after animation animateObserver.unobserve(entry.target); } });});// Observe all elements with data-animate attributedocument.querySelectorAll("[data-animate]").forEach(el => { animateObserver.observe(el);});// Track visibility percentageconst percentageObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { const percent = Math.round(entry.intersectionRatio * 100); entry.target.textContent = `Visible: ${percent}%`; // Trigger at different thresholds if (percent >= 75) { entry.target.style.background = "green"; } else if (percent >= 50) { entry.target.style.background = "yellow"; } else if (percent > 0) { entry.target.style.background = "red"; } else { entry.target.style.background = "gray"; } });}, { "threshold": [0, 0.25, 0.5, 0.75, 1.0] });const trackedElement = document.querySelector(".tracked");percentageObserver.observe(trackedElement);
Note: Intersection Observer is asynchronous and efficient - much better than scroll events. Callback fires when intersection changes, not continuously. Use isIntersecting for simple visibility checks, intersectionRatio for percentage visible.
10.2 Intersection Observer Root Margin and Thresholds
Option
Type
Description
Default
root
Element | null
Element to use as viewport. null = browser viewport.
null
rootMargin
string
Margin around root (CSS syntax). Can be negative. Example: "50px 0px -50px 0px".
"0px"
threshold
number | number[]
Intersection ratio(s) to trigger callback. 0.0 to 1.0. Can be array for multiple thresholds.
0
Threshold Value
Meaning
Use Case
0
Callback fires when any pixel is visible.
Detect element entering/leaving viewport
0.5
Callback fires when 50% of element is visible.
Trigger when element half-visible
1.0
Callback fires when 100% of element is visible.
Trigger when element fully-visible
[0, 0.25, 0.5, 0.75, 1.0]
Callback fires at 0%, 25%, 50%, 75%, 100% visibility.
Track visibility percentage changes
Example: Root margin and thresholds
// Preload images before they enter viewport (positive rootMargin)const preloadObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; // Load image 200px before it enters viewport if (img.dataset.src) { img.src = img.dataset.src; img.removeAttribute("data-src"); } preloadObserver.unobserve(img); } });}, { "rootMargin": "200px" // Expand viewport by 200px on all sides});document.querySelectorAll("img[data-src]").forEach(img => { preloadObserver.observe(img);});// Trigger only when element fully visibleconst fullyVisibleObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.intersectionRatio === 1.0) { console.log("Element 100% visible:", entry.target); entry.target.classList.add("fully-visible"); } else { entry.target.classList.remove("fully-visible"); } });}, { "threshold": 1.0});// Negative rootMargin to shrink viewportconst stickyHeaderObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { // Trigger 80px before element reaches top if (!entry.isIntersecting) { document.body.classList.add("sticky-header"); } else { document.body.classList.remove("sticky-header"); } });}, { "rootMargin": "-80px 0px 0px 0px" // Shrink top by 80px});const sentinelElement = document.querySelector(".sentinel");stickyHeaderObserver.observe(sentinelElement);// Multiple thresholds for progress trackingconst progressObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { const percent = Math.round(entry.intersectionRatio * 100); console.log(`Element ${percent}% visible`); // Update progress bar entry.target.querySelector(".progress").style.width = `${percent}%`; });}, { "threshold": Array.from({ "length": 101 }, (_, i) => i / 100) // 0, 0.01, 0.02, ..., 1.0});// Custom root containerconst scrollContainer = document.querySelector(".scroll-container");const containerObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { console.log("Visible in container:", entry.isIntersecting); });}, { "root": scrollContainer, // Use custom container instead of viewport "threshold": 0.5});const containerItem = scrollContainer.querySelector(".item");containerObserver.observe(containerItem);
Note:rootMargin uses CSS margin syntax - can be 1, 2, or 4 values. Positive values expand viewport (preload), negative shrink (trigger earlier). threshold can be single number or array. More thresholds = more callback invocations.
Warning: Don't use too many thresholds (e.g., 100+ values) - impacts performance. threshold: 1.0 may never trigger if element larger than viewport. Use rootMargin carefully with negative values - can cause immediate firing.
Border box dimensions (includes padding and border). Array for multi-column.
contentBoxSize
ResizeObserverSize[]
Content box dimensions. Array for multi-column.
devicePixelContentBoxSize
ResizeObserverSize[]
Content box in device pixels (for high-DPI displays).
ResizeObserverSize Property
Type
Description
inlineSize
number
Inline size (width in horizontal writing mode).
blockSize
number
Block size (height in horizontal writing mode).
Example: Basic resize observer
// Observe element size changesconst resizeObserver = new ResizeObserver((entries) => { entries.forEach(entry => { // Legacy way (deprecated but widely supported) const { "width": width, "height": height } = entry.contentRect; console.log(`Content size: ${width}x${height}`); // Modern way (preferred) if (entry.contentBoxSize) { // contentBoxSize is array (for multi-column) const contentBoxSize = entry.contentBoxSize[0]; console.log(`Inline size: ${contentBoxSize.inlineSize}px`); console.log(`Block size: ${contentBoxSize.blockSize}px`); } // Border box size (includes padding and border) if (entry.borderBoxSize) { const borderBoxSize = entry.borderBoxSize[0]; console.log(`Border box: ${borderBoxSize.inlineSize}x${borderBoxSize.blockSize}`); } // React to size changes const element = entry.target; if (contentBoxSize.inlineSize < 400) { element.classList.add("small"); element.classList.remove("large"); } else { element.classList.add("large"); element.classList.remove("small"); } });});// Observe elementconst box = document.querySelector(".resizable-box");resizeObserver.observe(box);// Observe with specific box modelresizeObserver.observe(box, { "box": "border-box" });// Stop observingresizeObserver.unobserve(box);resizeObserver.disconnect();
Example: Responsive component behavior
// Container queries with ResizeObserverclass ResponsiveComponent { constructor(element) { this.element = element; this.observer = new ResizeObserver(this.handleResize.bind(this)); this.observer.observe(this.element); } handleResize(entries) { const entry = entries[0]; const width = entry.contentBoxSize[0].inlineSize; // Remove all size classes this.element.classList.remove("size-sm", "size-md", "size-lg", "size-xl"); // Add appropriate class based on width if (width < 400) { this.element.classList.add("size-sm"); } else if (width < 768) { this.element.classList.add("size-md"); } else if (width < 1024) { this.element.classList.add("size-lg"); } else { this.element.classList.add("size-xl"); } // Dispatch custom event this.element.dispatchEvent(new CustomEvent("sizechange", { "detail": { "width": width, "breakpoint": this.getBreakpoint(width) } })); } getBreakpoint(width) { if (width < 400) return "sm"; if (width < 768) return "md"; if (width < 1024) return "lg"; return "xl"; } disconnect() { this.observer.disconnect(); }}// Usageconst component = new ResponsiveComponent(document.querySelector(".component"));component.element.addEventListener("sizechange", (e) => { console.log(`Breakpoint: ${e.detail.breakpoint}, Width: ${e.detail.width}px`);});// Sync chart size with containerconst chartContainer = document.querySelector(".chart-container");const chartObserver = new ResizeObserver((entries) => { const entry = entries[0]; const { "inlineSize": width, "blockSize": height } = entry.contentBoxSize[0]; // Resize chart to match container myChart.resize(width, height);});chartObserver.observe(chartContainer);
Note: Resize Observer is much more efficient than window resize events or polling. Fires when element size changes due to any reason (CSS, content, viewport). Use contentBoxSize array (modern) instead of contentRect (legacy).
Warning: Avoid infinite loops - don't resize observed element inside callback (can cause circular updates). ResizeObserver fires frequently - debounce expensive operations. Disconnect observers when component unmounts to prevent memory leaks.
10.4 Observer Patterns and Performance Best Practices
Best Practice
Why
Implementation
Single observer instance
More efficient than multiple observers for same purpose.
Reuse one observer for all similar elements.
Unobserve after use
Reduces overhead for one-time observations.
Call unobserve() after first trigger.
Disconnect on cleanup
Prevents memory leaks in SPAs.
Call disconnect() when component unmounts.
Lightweight callbacks
Observers fire frequently - expensive callbacks cause jank.
Keep callback logic minimal, defer heavy work.
Debounce expensive operations
ResizeObserver can fire many times per second.
Use debounce/throttle for expensive updates.
Example: Observer management patterns
// Singleton pattern - reuse observer across appclass ObserverManager { constructor() { this.intersectionObserver = null; this.resizeObserver = null; this.observedElements = new Map(); } // Get or create intersection observer getIntersectionObserver(options = {}) { if (!this.intersectionObserver) { this.intersectionObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { const callback = this.observedElements.get(entry.target); if (callback) { callback(entry); } }); }, options); } return this.intersectionObserver; } // Observe with custom callback observeIntersection(element, callback) { const observer = this.getIntersectionObserver(); this.observedElements.set(element, callback); observer.observe(element); } // Unobserve unobserveIntersection(element) { if (this.intersectionObserver) { this.intersectionObserver.unobserve(element); this.observedElements.delete(element); } } // Cleanup destroy() { if (this.intersectionObserver) { this.intersectionObserver.disconnect(); this.intersectionObserver = null; } if (this.resizeObserver) { this.resizeObserver.disconnect(); this.resizeObserver = null; } this.observedElements.clear(); }}// Global instanceconst observerManager = new ObserverManager();// Usage across appobserverManager.observeIntersection(element1, (entry) => { console.log("Element 1 intersecting:", entry.isIntersecting);});observerManager.observeIntersection(element2, (entry) => { console.log("Element 2 intersecting:", entry.isIntersecting);});// Debounce helper for resize observerfunction debounce(func, wait) { let timeout; return function executedFunction(...args) { clearTimeout(timeout); timeout = setTimeout(() => func(...args), wait); };}// Debounced resize handlingconst resizeObserver = new ResizeObserver(debounce((entries) => { entries.forEach(entry => { // Expensive operation - only runs after 200ms of no resize console.log("Resize complete:", entry.contentBoxSize); updateChart(entry.target); });}, 200));// Cleanup pattern for React/Vue/etcclass ComponentWithObserver { constructor(element) { this.element = element; this.observer = new IntersectionObserver(this.handleIntersection.bind(this)); this.observer.observe(this.element); } handleIntersection(entries) { entries.forEach(entry => { console.log("Intersecting:", entry.isIntersecting); }); } // Call on component unmount destroy() { this.observer.disconnect(); this.observer = null; }}// Usage in frameworkclass MyComponent { mounted() { this.componentObserver = new ComponentWithObserver(this.$el); } unmounted() { this.componentObserver.destroy(); }}
Note: Observers are more efficient than event listeners for visibility/size tracking. Use one observer instance for multiple elements when possible. Always disconnect observers on cleanup to prevent memory leaks in SPAs.
10.5 Lazy Loading Implementation with Observers
Lazy Loading Strategy
Description
Image Lazy Loading
Load images only when they enter or approach viewport using data-src attribute.
Native Lazy Loading
Use <img loading="lazy"> for simple cases (less control).
Background Image Loading
Apply background images via CSS class when element becomes visible.
Component Lazy Loading
Dynamically import and render components when they enter viewport.
Video/Iframe Lazy Loading
Defer loading heavy media until needed to improve initial page load.
rootMargin Preloading
Use positive rootMargin (e.g., "100px") to start loading before element visible.
Note: Native lazy loading available with <img loading="lazy"> but Intersection Observer provides more control. Use rootMargin to preload before entering viewport. Always unobserve after loading to free resources.
10.6 Infinite Scrolling with Intersection Observer
Infinite Scroll Pattern
Description
Sentinel Element
Place invisible element at bottom of list; observe it to trigger loading.
Loading State
Track loading state to prevent multiple simultaneous requests.
hasMore Flag
Boolean flag to stop loading when no more items available.
rootMargin Trigger
Use rootMargin to trigger loading before sentinel reaches viewport (better UX).
Error Handling
Show error message and retry button if loading fails.
End of List Indicator
Display message when all items loaded and stop observing.
Note: Use sentinel element at bottom of list to trigger loading. Add rootMargin to start loading before user reaches end. Track loading state to prevent duplicate requests. Always provide visual feedback (loading spinner).
Warning: Infinite scroll can hurt performance with thousands of DOM nodes. Consider virtualization for very long lists. Provide "back to top" button for usability. Be careful with browser history - users expect back button to work.
Observer API Best Practices
Use Intersection Observer instead of scroll events - much more efficient
Use Resize Observer instead of window resize events or polling
Reuse single observer instance for multiple elements of same type
Call unobserve() after one-time observations to reduce overhead
Always disconnect() observers on component unmount to prevent memory leaks
Note: Custom element names must contain hyphen (e.g., "my-element" not "myelement"). Constructor runs before element attached to DOM - don't access children or attributes there. Use connectedCallback for setup. Customized built-ins not supported in Safari.
Warning: Don't call super() without arguments in constructor. Don't add attributes in constructor - use connectedCallback. connectedCallback can be called multiple times if element moved. Always cleanup in disconnectedCallback.
11.2 Shadow DOM Encapsulation and Styling
Method/Property
Description
Browser Support
element.attachShadow(options)
Creates shadow root. Options: mode ("open" or "closed"), delegatesFocus.
All Modern Browsers
element.shadowRoot
Returns shadow root if mode is "open", null if "closed".
All Modern Browsers
shadowRoot.mode
"open" or "closed". Determines if shadowRoot accessible from outside.
All Modern Browsers
shadowRoot.host
Returns host element of shadow root.
All Modern Browsers
Shadow DOM Feature
Description
Benefit
Style Encapsulation
Styles inside shadow DOM don't affect outside, and vice versa.
Prevent CSS conflicts
DOM Encapsulation
Shadow DOM not accessible via querySelector from outside.
Implementation hiding
Event Retargeting
Events from shadow DOM appear to come from host element.
Note: Use mode: "open" for better accessibility and debugging. Shadow DOM styles don't inherit from outside except inheritable properties (color, font-family, etc.). Use ::part() to expose styling hooks. :host-context() has limited browser support.
Warning: Not all elements can have shadow DOM (e.g., <input>, <img>). Closed shadow DOM still accessible via DevTools - not security feature. Events from shadow DOM appear to originate from host (event retargeting).
11.3 HTML Templates and Template Element
Element/Property
Description
Browser Support
<template>
HTML element that holds client-side content not rendered on page load.
All Browsers
template.content
Returns DocumentFragment containing template's DOM subtree.
All Browsers
document.importNode(node, deep)
Clones node from another document. Use with template content.
Note: Template content is inert - scripts don't run, images don't load, styles don't apply until cloned into document. Use cloneNode(true) to deep clone template content. Templates can be defined in HTML or created programmatically.
11.4 Slot API for Content Projection
Element/Attribute
Description
Use Case
<slot>
Placeholder for projected content from light DOM.
Content projection
<slot name="...">
Named slot for specific content.
Multiple projection points
slot="name"
Attribute on light DOM element to target named slot.
Target specific slot
Slot Method/Property
Description
slot.assignedNodes(options)
Returns array of nodes assigned to slot. Options: flatten (include nested slots).
slot.assignedElements(options)
Returns array of elements (not text nodes) assigned to slot.
element.assignedSlot
Returns <slot> element that element is assigned to.
Note: Slots enable content projection - light DOM content rendered in shadow DOM. Unnamed slots are default slots. Multiple elements can target same named slot. Use ::slotted() to style slotted content from shadow DOM.
Warning:::slotted() can only select direct children, not descendants. Slotted elements remain in light DOM - only rendered location changes. Slot fallback content shown when no content assigned to slot.
11.5 Element Internals for Form Integration
Method/Property
Description
Browser Support
element.attachInternals()
Returns ElementInternals object for form/accessibility integration.
All Modern Browsers
static formAssociated = true
Marks element as form-associated. Required for form integration.
All Modern Browsers
ElementInternals Property/Method
Description
form
Returns associated <form> element.
setFormValue(value, state)
Sets element's form value. Optional state for internal representation.
Browser restores state. Mode: "restore" or "autocomplete".
Example: Form-associated custom element
// Custom input element with form integrationclass CustomInput extends HTMLElement { // Must set formAssociated to true static formAssociated = true; constructor() { super(); // Attach internals for form integration this._internals = this.attachInternals(); const shadow = this.attachShadow({ "mode": "open" }); shadow.innerHTML = ` <style> input { padding: 8px; border: 1px solid #ccc; border-radius: 4px; } input:invalid { border-color: red; } .error { color: red; font-size: 12px; } </style> <input type="text" /> <div class="error"></div> `; this._input = shadow.querySelector("input"); this._error = shadow.querySelector(".error"); // Sync internal input with form value this._input.addEventListener("input", () => { this.value = this._input.value; }); } // Form lifecycle callbacks formAssociatedCallback(form) { console.log("Associated with form:", form); } formResetCallback() { console.log("Form reset"); this.value = ""; } formDisabledCallback(disabled) { console.log("Disabled:", disabled); this._input.disabled = disabled; } formStateRestoreCallback(state, mode) { console.log("State restore:", state, mode); this.value = state; } // Value getter/setter get value() { return this._input.value; } set value(val) { this._input.value = val; // Update form value this._internals.setFormValue(val); // Validate this.validate(); } // Validation validate() { const value = this.value; if (this.hasAttribute("required") && !value) { this._internals.setValidity( { "valueMissing": true }, "This field is required", this._input ); this._error.textContent = "This field is required"; return false; } const minLength = parseInt(this.getAttribute("minlength")) || 0; if (value.length < minLength) { this._internals.setValidity( { "tooShort": true }, `Minimum ${minLength} characters required`, this._input ); this._error.textContent = `Minimum ${minLength} characters required`; return false; } // Valid this._internals.setValidity({}); this._error.textContent = ""; return true; } // Standard form methods checkValidity() { return this._internals.checkValidity(); } reportValidity() { return this._internals.reportValidity(); } // Access to form get form() { return this._internals.form; } get validity() { return this._internals.validity; } get validationMessage() { return this._internals.validationMessage; }}customElements.define("custom-input", CustomInput);// Usage:// <form>// <custom-input name="username" required minlength="3"></custom-input>// <button type="submit">Submit</button>// </form>// Access form valueconst form = document.querySelector("form");form.addEventListener("submit", (e) => { e.preventDefault(); const formData = new FormData(form); console.log("Username:", formData.get("username"));});
Note: Set static formAssociated = true to enable form integration. Use attachInternals() to get ElementInternals object. Custom elements integrate with native form features - validation, FormData, submit events. Use setFormValue() to sync with form.
Warning: Can only call attachInternals() once. Must set formAssociated before element registered. Form callbacks may not fire in all scenarios - test thoroughly. ElementInternals has good browser support but check for older browsers.
Note: Adoptable Stylesheets enable stylesheet reuse across shadow roots - better performance than duplicating <style> tags. Stylesheets created once, shared across components. Use replaceSync() for initial load, replace() for async updates.
Warning: Changes to shared stylesheet affect all components using it - be careful with dynamic updates. adoptedStyleSheets replaces array - use spread to add: [...shadow.adoptedStyleSheets, newSheet]. Constructable stylesheets not available in older browsers.
Web Components Best Practices
Always use hyphenated names for custom elements - required by spec
Use mode: "open" for shadow DOM - better accessibility and debugging
Initialize in constructor, setup in connectedCallback
Always cleanup in disconnectedCallback - remove listeners, cancel timers
Use attributeChangedCallback with observedAttributes for reactive attributes
Use templates to avoid creating DOM strings repeatedly
Use slots for content projection - more flexible than imperative DOM manipulation
Use ::part() to expose styling hooks for component consumers
Set formAssociated = true for form-integrated custom elements
Use adoptable stylesheets to share styles across components efficiently
Avoid customized built-ins - not supported in Safari
Test components in isolation and with various content projections
12. Authentication and Security APIs
12.1 Web Authentication (WebAuthn) API
Method
Description
Browser Support
navigator.credentials.create(options)
Creates new credential (registration). Options include publicKey for WebAuthn.
All Modern Browsers
navigator.credentials.get(options)
Gets credential for authentication. Options include publicKey for WebAuthn.
All Modern Browsers
PublicKeyCredential Property
Type
Description
id
string
Base64url-encoded credential ID.
rawId
ArrayBuffer
Raw credential ID bytes.
type
string
Always "public-key" for WebAuthn.
response
AuthenticatorResponse
Authenticator response (attestation or assertion).
Note: WebAuthn enables passwordless authentication using biometrics or security keys. More secure than passwords - phishing resistant. Requires HTTPS. Server must validate attestation/assertion. Use platform authenticators (Face ID, Touch ID, Windows Hello) for best UX.
Warning: WebAuthn requires server-side implementation for challenge generation and verification. Always use proper challenge validation to prevent replay attacks. Test on multiple platforms - behavior varies. Fallback to password auth for browsers without support.
12.2 Credential Management API for Password Storage
Method
Description
Browser Support
navigator.credentials.create(options)
Creates credential. Options: password for PasswordCredential.
// Auto sign-in on page loadasync function autoSignIn() { try { // Get stored credential const credential = await navigator.credentials.get({ "password": true, "mediation": "optional" // "optional", "silent", or "required" }); if (credential) { console.log("Credential found:", credential.id); // Use credential to sign in const response = await fetch("/auth/login", { "method": "POST", "headers": { "Content-Type": "application/json" }, "body": JSON.stringify({ "username": credential.id, "password": credential.password }) }); if (response.ok) { console.log("Auto sign-in successful"); return true; } } else { console.log("No stored credentials"); } } catch (error) { console.error("Auto sign-in failed:", error); } return false;}// Store credential after successful registration/loginasync function storeCredential(username, password, name) { try { // Create password credential const credential = new PasswordCredential({ "id": username, "password": password, "name": name, "iconURL": "/images/avatar.png" }); // Store credential await navigator.credentials.store(credential); console.log("Credential stored"); return true; } catch (error) { console.error("Failed to store credential:", error); return false; }}// Alternative: create from formasync function storeCredentialFromForm() { const form = document.querySelector("form"); try { const credential = await navigator.credentials.create({ "password": form }); await navigator.credentials.store(credential); console.log("Credential from form stored"); } catch (error) { console.error("Failed to store credential from form:", error); }}// Prevent auto sign-in after logoutasync function signOut() { // Prevent silent access on next visit await navigator.credentials.preventSilentAccess(); console.log("Signed out - auto sign-in disabled"); // Clear session await fetch("/auth/logout", { "method": "POST" });}// Check supportif (window.PasswordCredential) { console.log("Credential Management API supported"); // Try auto sign-in autoSignIn().then(success => { if (!success) { // Show login form document.querySelector(".login-form").style.display = "block"; } });} else { console.log("Credential Management API not supported");}
Note: Credential Management API has limited browser support (Chrome, Edge). Integrates with browser's password manager. Use mediation: "silent" for automatic sign-in, "required" to always show account picker. Always call preventSilentAccess() on sign-out.
Warning: API doesn't store passwords itself - relies on browser's password manager. Not available in Safari/Firefox. Always provide fallback manual login. Don't rely on this for security - still validate on server.
Note: Payment Request API provides native payment UI across browsers. Supports credit cards, digital wallets (Apple Pay, Google Pay). Must call show() from user gesture. Always call complete() to close payment UI.
Warning: Payment Request API only handles UI - doesn't process payments. Must implement server-side payment processing. Never trust client-side data - validate on server. Test on mobile - UX varies by platform.
// Check permission statusasync function checkPermission(name) { try { const status = await navigator.permissions.query({ "name": name }); console.log(`${name} permission:`, status.state); // Listen for changes status.addEventListener("change", () => { console.log(`${name} permission changed to:`, status.state); }); return status.state; } catch (error) { console.error(`Failed to query ${name} permission:`, error); return null; }}// Camera permissionasync function requestCameraAccess() { const status = await checkPermission("camera"); if (status === "granted") { console.log("Camera already granted"); return true; } else if (status === "denied") { console.log("Camera denied - show instructions to enable"); return false; } else if (status === "prompt") { console.log("Camera will prompt user"); // Request access (triggers permission prompt) try { const stream = await navigator.mediaDevices.getUserMedia({ "video": true }); stream.getTracks().forEach(track => track.stop()); return true; } catch (error) { console.error("Camera access denied:", error); return false; } }}// Geolocation permissionasync function requestLocationAccess() { const status = await navigator.permissions.query({ "name": "geolocation" }); console.log("Geolocation permission:", status.state); if (status.state === "granted") { // Already granted - get location navigator.geolocation.getCurrentPosition( (position) => console.log("Location:", position.coords), (error) => console.error("Location error:", error) ); } else { // Will prompt or is denied navigator.geolocation.getCurrentPosition( (position) => console.log("Location granted:", position.coords), (error) => console.error("Location denied:", error) ); }}// Notification permissionasync function requestNotificationAccess() { const status = await navigator.permissions.query({ "name": "notifications" }); console.log("Notification permission:", status.state); if (status.state === "prompt") { // Request permission const result = await Notification.requestPermission(); console.log("Notification permission result:", result); }}// Check multiple permissionsasync function checkMultiplePermissions() { const permissions = ["camera", "microphone", "geolocation", "notifications"]; const results = await Promise.all( permissions.map(async (name) => { try { const status = await navigator.permissions.query({ "name": name }); return { "name": name, "state": status.state }; } catch (error) { return { "name": name, "state": "unsupported" }; } }) ); console.log("Permission states:"); results.forEach(({ "name": name, "state": state }) => { console.log(` ${name}: ${state}`); }); return results;}// Permission-aware UIasync function updateUIBasedOnPermissions() { const cameraStatus = await checkPermission("camera"); const cameraButton = document.getElementById("camera-button"); if (cameraStatus === "granted") { cameraButton.textContent = "Open Camera"; cameraButton.disabled = false; } else if (cameraStatus === "denied") { cameraButton.textContent = "Camera Denied"; cameraButton.disabled = true; // Show instructions to enable in settings } else { cameraButton.textContent = "Allow Camera"; cameraButton.disabled = false; }}
Note: Permissions API only queries state - doesn't request permission. To request, use the actual API (getUserMedia, Notification.requestPermission, etc.). Use onchange to update UI when user changes permission in browser settings.
Warning: Not all permissions queryable on all browsers. Some permissions always return "prompt" state. Don't rely solely on Permissions API - handle permission errors when using actual APIs. Browser support varies by permission type.
12.5 Content Security Policy (CSP) Reporting API
Event
Description
Browser Support
securitypolicyviolation
Fires when CSP violation occurs. Event has violation details.
All Modern Browsers
SecurityPolicyViolationEvent Property
Type
Description
violatedDirective
string
CSP directive that was violated (e.g., "script-src").
Note: CSP violations fire securitypolicyviolation event. Use to monitor and fix CSP issues. Set CSP in report-only mode during development with Content-Security-Policy-Report-Only header. Aggregate violations before sending to reduce requests.
12.6 SubtleCrypto API for Cryptographic Operations
// Hash string with SHA-256async function hashString(message) { // Encode string to bytes const encoder = new TextEncoder(); const data = encoder.encode(message); // Hash data const hashBuffer = await crypto.subtle.digest("SHA-256", data); // Convert to hex string const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(b => b.toString(16).padStart(2, "0")).join(""); return hashHex;}// Usageconst hash = await hashString("Hello, World!");console.log("SHA-256 hash:", hash);
Example: Encrypt/decrypt with AES-GCM
// Generate AES-GCM keyasync function generateAESKey() { const key = await crypto.subtle.generateKey( { "name": "AES-GCM", "length": 256 }, true, // extractable ["encrypt", "decrypt"] ); return key;}// Encrypt dataasync function encryptData(key, data) { const encoder = new TextEncoder(); const encoded = encoder.encode(data); // Generate random IV (initialization vector) const iv = crypto.getRandomValues(new Uint8Array(12)); // Encrypt const encrypted = await crypto.subtle.encrypt( { "name": "AES-GCM", "iv": iv }, key, encoded ); // Return IV + encrypted data return { "iv": Array.from(iv), "data": Array.from(new Uint8Array(encrypted)) };}// Decrypt dataasync function decryptData(key, encryptedData) { const decrypted = await crypto.subtle.decrypt( { "name": "AES-GCM", "iv": new Uint8Array(encryptedData.iv) }, key, new Uint8Array(encryptedData.data) ); const decoder = new TextDecoder(); return decoder.decode(decrypted);}// Usageconst key = await generateAESKey();const encrypted = await encryptData(key, "Secret message");const decrypted = await decryptData(key, encrypted);console.log("Decrypted:", decrypted);
Note: SubtleCrypto API is for cryptographic operations - hashing, encryption, signing. Only available in secure contexts (HTTPS). Use AES-GCM for encryption, SHA-256 for hashing, RSA/ECDSA for signing. All methods return Promises.
Warning: Crypto is complex - use established libraries when possible. Never implement your own crypto algorithms. Always use random IVs for encryption. Don't use for password storage - use server-side bcrypt/argon2. SubtleCrypto requires HTTPS.
Authentication and Security Best Practices
Use WebAuthn for passwordless authentication - more secure than passwords
Implement proper challenge validation for WebAuthn on server
Credential Management API has limited support - provide fallback
Payment Request API only handles UI - implement server-side processing
Use Permissions API to check state before requesting - better UX
Monitor CSP violations with securitypolicyviolation event
Use SubtleCrypto for client-side encryption - requires HTTPS
Never implement custom crypto - use established algorithms
Always validate and sanitize data on server - never trust client
Use HTTPS for all security-sensitive operations
Test security features across browsers - support varies
Provide clear error messages when permissions denied
13. Communication and Sharing APIs
13.1 Broadcast Channel API for Tab Communication
Method/Property
Description
Browser Support
new BroadcastChannel(name)
Creates channel with specified name. All same-origin contexts with same name can communicate.
All Modern Browsers
channel.postMessage(data)
Sends message to all listeners on same channel. Data is cloned using structured clone algorithm.
All Modern Browsers
channel.onmessage
Event handler for incoming messages. Receives MessageEvent with data property.
All Modern Browsers
channel.close()
Closes channel and stops receiving messages. Should be called when done to free resources.
All Modern Browsers
channel.name
Read-only property returning channel name.
All Modern Browsers
Example: Tab synchronization with BroadcastChannel
// Create broadcast channel for tab syncconst channel = new BroadcastChannel("app-sync");// Listen for messageschannel.onmessage = (event) => { console.log("Received from another tab:", event.data); const { "type": type, "payload": payload } = event.data; switch (type) { case "USER_LOGGED_IN": updateUIForLoggedInUser(payload.user); break; case "USER_LOGGED_OUT": updateUIForLoggedOutUser(); break; case "CART_UPDATED": updateCart(payload.items); break; case "THEME_CHANGED": applyTheme(payload.theme); break; default: console.log("Unknown message type:", type); }};// Error handlingchannel.onerror = (event) => { console.error("BroadcastChannel error:", event);};// Send message to other tabsfunction notifyOtherTabs(type, payload) { channel.postMessage({ "type": type, "payload": payload, "timestamp": Date.now(), "tabId": sessionStorage.getItem("tabId") });}// Example usagedocument.getElementById("loginBtn").addEventListener("click", () => { const user = { "id": "123", "name": "John Doe" }; // Update current tab updateUIForLoggedInUser(user); // Notify other tabs notifyOtherTabs("USER_LOGGED_IN", { "user": user });});// Clean up when page unloadswindow.addEventListener("beforeunload", () => { channel.close();});
Note: BroadcastChannel enables same-origin tab communication without a server. Messages are not persistent - lost if no tabs listening. Use for real-time sync (login state, cart, settings). Works across tabs, windows, iframes. Data must be structured-cloneable (no functions).
13.2 Web Share API for Native Sharing
Method/Property
Description
Browser Support
navigator.canShare(data)
Returns boolean indicating if data can be shared. Check before calling share().
Modern Browsers
navigator.share(data)
Opens native share dialog. Data object can include title, text, url, files.
Modern Browsers
Share Data Property
Type
Description
title
string
Title for shared content. Optional.
text
string
Text/description to share. Optional.
url
string
URL to share. Optional.
files
File[]
Array of File objects to share. Optional. Check support with canShare().
Example: Share text and URL
// Check if Web Share API is supportedif (navigator.share) { console.log("Web Share API supported");} else { console.log("Web Share API not supported - show fallback UI");}// Share contentasync function shareContent() { // Must be called from user gesture (click, tap) const shareData = { "title": "Amazing Article", "text": "Check out this great article I found!", "url": "https://example.com/article/123" }; try { await navigator.share(shareData); console.log("Content shared successfully"); } catch (error) { if (error.name === "AbortError") { console.log("User cancelled share"); } else { console.error("Error sharing:", error); // Show fallback share options (copy link, social media buttons) showFallbackShareUI(shareData); } }}// Add event listenerdocument.getElementById("shareBtn").addEventListener("click", shareContent);
Example: Share files
// Share image fileasync function shareImage() { try { // Get or create file const response = await fetch("/images/photo.jpg"); const blob = await response.blob(); const file = new File([blob], "photo.jpg", { "type": "image/jpeg" }); // Check if files can be shared const shareData = { "files": [file], "title": "Beautiful Photo", "text": "Check out this photo!" }; if (!navigator.canShare) { throw new Error("Web Share API not supported"); } if (!navigator.canShare(shareData)) { throw new Error("Cannot share this file type"); } // Share await navigator.share(shareData); console.log("File shared successfully"); } catch (error) { console.error("Error sharing file:", error); // Fallback: download file or copy link showDownloadOption(file); }}// Share multiple filesasync function shareMultipleFiles(files) { const shareData = { "files": files, "title": "My Files" }; if (navigator.canShare && navigator.canShare(shareData)) { try { await navigator.share(shareData); console.log("Files shared"); } catch (error) { console.error("Share failed:", error); } } else { console.log("Cannot share files - browser limitation"); // Fallback to individual downloads }}// Share canvas as imageasync function shareCanvas(canvas) { canvas.toBlob(async (blob) => { const file = new File([blob], "drawing.png", { "type": "image/png" }); if (navigator.canShare && navigator.canShare({ "files": [file] })) { await navigator.share({ "files": [file], "title": "My Drawing" }); } });}
Note: Web Share API provides native sharing - opens OS share sheet on mobile. Must be triggered by user gesture (click/tap). Only works on HTTPS. Always provide fallback for unsupported browsers. File sharing support varies by platform.
Warning: Always check navigator.share availability before using. Use canShare() to verify data is shareable. User can cancel share - handle AbortError. Desktop browsers have limited support - mobile is primary use case. Provide copy-link fallback.
13.3 Web Share Target API for Receiving Shares
Manifest Property
Description
share_target
Defines how app receives shared content. Object with action, method, enctype, params.
share_target.action
URL to handle share (e.g., /share). Relative to app scope.
share_target.method
HTTP method: "GET" or "POST". Default: "GET".
share_target.enctype
For POST: "application/x-www-form-urlencoded" or "multipart/form-data" (for files).
share_target.params
Maps share data to query/form parameters: title, text, url, files.
// service-worker.js - Handle share targetself.addEventListener("fetch", (event) => { const url = new URL(event.request.url); // Check if this is a share request if (url.pathname === "/share" && event.request.method === "POST") { event.respondWith(handleShare(event.request)); }});async function handleShare(request) { try { const formData = await request.formData(); const title = formData.get("title") || ""; const text = formData.get("text") || ""; const url = formData.get("url") || ""; const mediaFiles = formData.getAll("media"); // Store shared content const shareData = { "title": title, "text": text, "url": url, "files": [], "timestamp": Date.now() }; // Process files for (const file of mediaFiles) { // Store file (could use IndexedDB, Cache API, etc.) const arrayBuffer = await file.arrayBuffer(); shareData.files.push({ "name": file.name, "type": file.type, "size": file.size, "data": arrayBuffer }); } // Store in IndexedDB const db = await openDatabase(); await storeSharedContent(db, shareData); // Redirect to app with share ID const shareId = Date.now(); return Response.redirect(`/app?share=${shareId}`, 303); } catch (error) { console.error("Error handling share:", error); return new Response("Error processing shared content", { "status": 500 }); }}
Example: Process shared content in app
// app.js - Process shared contentasync function checkForSharedContent() { const params = new URLSearchParams(window.location.search); const shareId = params.get("share"); if (shareId) { try { // Retrieve shared content from IndexedDB const db = await openDatabase(); const shareData = await getSharedContent(db, shareId); if (shareData) { console.log("Received shared content:", shareData); // Display shared content if (shareData.title) { document.getElementById("title").value = shareData.title; } if (shareData.text) { document.getElementById("content").value = shareData.text; } if (shareData.url) { document.getElementById("link").value = shareData.url; } if (shareData.files.length > 0) { displaySharedFiles(shareData.files); } // Clear URL parameter window.history.replaceState({}, "", "/app"); } } catch (error) { console.error("Error loading shared content:", error); } }}// Run on page loadcheckForSharedContent();
Note: Web Share Target API makes PWAs share targets - appear in OS share sheet. Requires web app manifest with share_target field. App must be installed to appear as share target. Use service worker to handle POST requests with files.
Warning: Only works for installed PWAs - not regular web pages. Limited browser support (mainly Chrome/Edge on Android). Files require POST with multipart/form-data. Test thoroughly on target platforms. Provide clear UI feedback for shared content.
13.4 MessageChannel and MessagePort APIs
Class/Method
Description
Browser Support
new MessageChannel()
Creates channel with two MessagePort objects for bidirectional communication.
All Browsers
channel.port1
First MessagePort of the channel.
All Browsers
channel.port2
Second MessagePort of the channel.
All Browsers
port.postMessage(data, [transfer])
Sends message through port. Optional transfer array for transferable objects.
All Browsers
port.onmessage
Event handler for messages. Port must be started via start() or setting onmessage.
All Browsers
port.start()
Starts port (enables message delivery). Called automatically when setting onmessage.
All Browsers
port.close()
Closes port and stops message delivery.
All Browsers
Example: Communication between iframe and parent
// Parent pageconst iframe = document.getElementById("myFrame");// Create channelconst channel = new MessageChannel();// Listen on port1channel.port1.onmessage = (event) => { console.log("Parent received:", event.data); // Send response channel.port1.postMessage({ "type": "RESPONSE", "data": "Hello from parent" });};// Transfer port2 to iframeiframe.contentWindow.postMessage( { "type": "INIT", "message": "Here's your port" }, "*", [channel.port2] // Transfer port ownership);// Send message to iframesetTimeout(() => { channel.port1.postMessage({ "type": "REQUEST", "data": "What's your status?" });}, 1000);// ===== iframe page =====// Listen for port from parentwindow.addEventListener("message", (event) => { if (event.data.type === "INIT") { // Get transferred port const port = event.ports[0]; // Listen on port port.onmessage = (e) => { console.log("Iframe received:", e.data); if (e.data.type === "REQUEST") { // Send response port.postMessage({ "type": "STATUS", "data": "All systems operational" }); } }; // Send initial message port.postMessage({ "type": "READY", "data": "Iframe initialized" }); }});
Example: Worker communication with MessageChannel
// Main threadconst worker = new Worker("worker.js");const channel = new MessageChannel();// Listen on port1channel.port1.onmessage = (event) => { console.log("Main thread received:", event.data); document.getElementById("result").textContent = event.data.result;};// Send port2 to workerworker.postMessage( { "type": "INIT_PORT" }, [channel.port2]);// Send work request via channelfunction processData(data) { channel.port1.postMessage({ "type": "PROCESS", "data": data });}document.getElementById("processBtn").addEventListener("click", () => { processData([1, 2, 3, 4, 5]);});// ===== worker.js =====let port = null;self.onmessage = (event) => { if (event.data.type === "INIT_PORT") { // Get transferred port port = event.ports[0]; // Listen on port port.onmessage = (e) => { if (e.data.type === "PROCESS") { // Process data const result = e.data.data.reduce((sum, num) => sum + num, 0); // Send result back port.postMessage({ "type": "RESULT", "result": result }); } }; // Notify ready port.postMessage({ "type": "READY", "message": "Worker ready for processing" }); }};
Example: Dedicated communication channels
// Create multiple channels for different purposesclass ChannelManager { constructor() { this.channels = new Map(); } createChannel(name) { const channel = new MessageChannel(); this.channels.set(name, channel); return channel; } getChannel(name) { return this.channels.get(name); } sendMessage(channelName, portNumber, data) { const channel = this.channels.get(channelName); if (channel) { const port = portNumber === 1 ? channel.port1 : channel.port2; port.postMessage(data); } } cleanup() { this.channels.forEach(channel => { channel.port1.close(); channel.port2.close(); }); this.channels.clear(); }}// Usageconst manager = new ChannelManager();// Create channels for different modulesconst authChannel = manager.createChannel("auth");const dataChannel = manager.createChannel("data");// Setup listenersauthChannel.port1.onmessage = (event) => { console.log("Auth message:", event.data);};dataChannel.port1.onmessage = (event) => { console.log("Data message:", event.data);};// Transfer ports to workerworker.postMessage( { "type": "SETUP" }, [authChannel.port2, dataChannel.port2]);// Clean up when donewindow.addEventListener("beforeunload", () => { manager.cleanup();});
Note: MessageChannel creates dedicated communication pipe with two ports. Use for structured communication between contexts (workers, iframes). Ports are transferable - ownership can be transferred. More efficient than postMessage for ongoing communication.
Sends message to another window/iframe. Always specify targetOrigin for security.
Cross-origin iframe communication
window.addEventListener("message", handler)
Listens for postMessage events. Always validate event.origin.
Receiving cross-origin messages
event.origin
Origin of message sender. Validate before processing message.
Security check
event.source
Reference to window that sent message. Use to send response.
Bidirectional communication
event.data
Message data (structured clone).
Payload
event.ports
Array of transferred MessagePort objects.
Channel establishment
Example: Secure cross-origin iframe communication
// Parent page (https://parent.com)const iframe = document.getElementById("childFrame");const ALLOWED_ORIGIN = "https://child.com";// Listen for messages from iframewindow.addEventListener("message", (event) => { // CRITICAL: Always validate origin if (event.origin !== ALLOWED_ORIGIN) { console.warn("Message from unauthorized origin:", event.origin); return; } console.log("Received from iframe:", event.data); // Process message const { "type": type, "payload": payload } = event.data; switch (type) { case "REQUEST_USER_DATA": // Send user data to iframe event.source.postMessage({ "type": "USER_DATA", "payload": { "userId": "123", "name": "John Doe" } }, ALLOWED_ORIGIN); break; case "HEIGHT_CHANGED": // Resize iframe iframe.style.height = payload.height + "px"; break; default: console.log("Unknown message type:", type); }});// Send message to iframefunction sendToIframe(data) { iframe.contentWindow.postMessage(data, ALLOWED_ORIGIN);}// Wait for iframe to loadiframe.addEventListener("load", () => { sendToIframe({ "type": "INIT", "payload": { "theme": "dark" } });});// ===== Iframe page (https://child.com) =====const PARENT_ORIGIN = "https://parent.com";// Listen for messages from parentwindow.addEventListener("message", (event) => { // Validate origin if (event.origin !== PARENT_ORIGIN) { console.warn("Message from unauthorized origin:", event.origin); return; } const { "type": type, "payload": payload } = event.data; switch (type) { case "INIT": // Initialize with config from parent applyTheme(payload.theme); // Request user data window.parent.postMessage({ "type": "REQUEST_USER_DATA" }, PARENT_ORIGIN); break; case "USER_DATA": // Use received data displayUserInfo(payload); break; }});// Notify parent of height changesconst resizeObserver = new ResizeObserver((entries) => { const height = entries[0].contentRect.height; window.parent.postMessage({ "type": "HEIGHT_CHANGED", "payload": { "height": height } }, PARENT_ORIGIN);});resizeObserver.observe(document.body);
Example: Cross-origin authentication flow
// Main app - open OAuth popupfunction initiateOAuth() { const authWindow = window.open( "https://auth-provider.com/oauth", "oauth", "width=600,height=700" ); // Listen for auth result const messageHandler = (event) => { // Validate origin if (event.origin !== "https://auth-provider.com") { return; } if (event.data.type === "OAUTH_SUCCESS") { console.log("OAuth successful:", event.data.token); // Store token localStorage.setItem("authToken", event.data.token); // Close popup authWindow.close(); // Clean up listener window.removeEventListener("message", messageHandler); // Update UI onAuthenticationSuccess(event.data.user); } else if (event.data.type === "OAUTH_ERROR") { console.error("OAuth failed:", event.data.error); authWindow.close(); window.removeEventListener("message", messageHandler); onAuthenticationError(event.data.error); } }; window.addEventListener("message", messageHandler);}// ===== OAuth provider page =====function completeOAuth(token, user) { // Send result to opener if (window.opener) { window.opener.postMessage({ "type": "OAUTH_SUCCESS", "token": token, "user": user }, "https://main-app.com"); // Specify target origin // Close self window.close(); }}function failOAuth(error) { if (window.opener) { window.opener.postMessage({ "type": "OAUTH_ERROR", "error": error }, "https://main-app.com"); window.close(); }}
Note:postMessage enables secure cross-origin communication. Always specify targetOrigin when sending. Always validate event.origin when receiving. Use structured data with type field. Common for iframes, popups, worker communication.
Warning: NEVER use targetOrigin: "*" with sensitive data - major security risk. Always validate event.origin before processing messages. Don't trust message content - validate and sanitize. Be aware of timing attacks. Use MessageChannel for long-term communication.
13.6 Channel Messaging API for Worker Communication
Pattern
Description
Use Case
Worker postMessage
Direct message to worker via worker.postMessage(). Simple but limited to one-way request-response.
Simple worker tasks
MessageChannel with Workers
Create dedicated channel for bidirectional communication. More structured than direct postMessage.
Complex worker interactions
SharedWorker Communication
Multiple tabs/windows connect to same worker. Each gets own MessagePort.
Cross-tab shared state
BroadcastChannel in Worker
Workers can use BroadcastChannel to communicate with main thread and other workers.
Multi-worker coordination
Example: SharedWorker with MessagePort
// shared-worker.jsconst connections = new Set();self.addEventListener("connect", (event) => { const port = event.ports[0]; connections.add(port); console.log("New connection. Total:", connections.size); port.addEventListener("message", (e) => { const { "type": type, "data": data } = e.data; switch (type) { case "BROADCAST": // Send to all connected ports except sender connections.forEach(p => { if (p !== port) { p.postMessage({ "type": "MESSAGE", "data": data }); } }); break; case "GET_CONNECTION_COUNT": port.postMessage({ "type": "CONNECTION_COUNT", "count": connections.size }); break; } }); port.start(); // Handle disconnect port.addEventListener("close", () => { connections.delete(port); console.log("Connection closed. Remaining:", connections.size); });});// ===== Main thread usage =====// Connect to shared workerconst worker = new SharedWorker("shared-worker.js");const port = worker.port;port.addEventListener("message", (event) => { console.log("Received from shared worker:", event.data); if (event.data.type === "MESSAGE") { displayMessage(event.data.data); }});port.start();// Send broadcast messagefunction broadcastMessage(message) { port.postMessage({ "type": "BROADCAST", "data": message });}// Get connection countfunction getConnectionCount() { port.postMessage({ "type": "GET_CONNECTION_COUNT" });}
Example: Multi-worker coordination
// Main thread - coordinate multiple workersclass WorkerPool { constructor(scriptUrl, size) { this.workers = []; this.channels = []; this.taskQueue = []; this.busyWorkers = new Set(); // Create workers and channels for (let i = 0; i < size; i++) { const worker = new Worker(scriptUrl); const channel = new MessageChannel(); // Setup worker communication worker.postMessage( { "type": "INIT", "workerId": i }, [channel.port2] ); // Listen on port1 channel.port1.onmessage = (event) => { this.handleWorkerMessage(i, event.data); }; this.workers.push(worker); this.channels.push(channel); } } handleWorkerMessage(workerId, data) { if (data.type === "TASK_COMPLETE") { console.log(`Worker ${workerId} completed task:`, data.result); // Mark worker as available this.busyWorkers.delete(workerId); // Process callback if (data.taskId && this.callbacks.has(data.taskId)) { this.callbacks.get(data.taskId)(data.result); this.callbacks.delete(data.taskId); } // Process next task this.processNextTask(); } } async executeTask(taskData) { return new Promise((resolve) => { const taskId = Date.now() + Math.random(); this.taskQueue.push({ "id": taskId, "data": taskData, "callback": resolve }); this.callbacks = this.callbacks || new Map(); this.callbacks.set(taskId, resolve); this.processNextTask(); }); } processNextTask() { if (this.taskQueue.length === 0) return; // Find available worker const availableWorker = this.workers.findIndex( (_, index) => !this.busyWorkers.has(index) ); if (availableWorker === -1) return; // All busy const task = this.taskQueue.shift(); this.busyWorkers.add(availableWorker); // Send task to worker this.channels[availableWorker].port1.postMessage({ "type": "TASK", "taskId": task.id, "data": task.data }); } terminate() { this.workers.forEach(worker => worker.terminate()); this.channels.forEach(channel => { channel.port1.close(); }); }}// Usageconst pool = new WorkerPool("task-worker.js", 4);// Execute tasksasync function processManyTasks() { const tasks = Array.from({ "length": 100 }, (_, i) => ({ "operation": "calculate", "value": i })); const results = await Promise.all( tasks.map(task => pool.executeTask(task)) ); console.log("All tasks completed:", results);}processManyTasks();
Note: Use MessageChannel for structured worker communication. SharedWorker enables cross-tab communication via MessagePorts. Each connection gets dedicated port. Good for coordinating work across tabs or managing worker pools.
Warning: SharedWorker has limited browser support (no Safari). Always call port.start() for SharedWorker ports. Clean up ports and workers when done to prevent memory leaks. Test worker communication error scenarios.
Communication and Sharing Best Practices
Use BroadcastChannel for simple same-origin tab sync - logout, cart updates
Web Share API must be triggered by user gesture - provide fallback
Check canShare() before attempting to share files
Web Share Target requires installed PWA - test on target platforms
MessageChannel more efficient than postMessage for ongoing communication
Always validate event.origin in postMessage handlers - critical security
Never use targetOrigin: "*" with sensitive data
SharedWorker enables cross-tab state but limited browser support
Use structured message format with type field for all communication
Call port.start() when using addEventListener instead of onmessage
Clean up channels, ports, and listeners to prevent memory leaks
Provide clear error handling for communication failures
14. Notification and Messaging APIs
14.1 Notification API for Desktop Notifications
Method/Property
Description
Browser Support
Notification.permission
Current permission state: "granted", "denied", or "default".
All Modern Browsers
Notification.requestPermission()
Requests notification permission. Returns Promise resolving to permission state.
All Modern Browsers
new Notification(title, options)
Creates and displays notification. Options include body, icon, badge, tag, etc.
All Modern Browsers
notification.close()
Closes notification programmatically.
All Modern Browsers
Notification Option
Type
Description
body
string
Notification body text.
icon
string
URL to icon image. Typically 192x192px.
badge
string
URL to badge icon for notification area. Monochrome, 96x96px.
tag
string
ID for notification grouping. Same tag replaces previous notification.
data
any
Arbitrary data to associate with notification. Accessible in event handlers.
requireInteraction
boolean
If true, notification persists until user interacts. Default: false.
actions
array
Array of action objects with action, title, icon. Service Worker only.
Note: Notification API shows desktop notifications to users. Requires user permission - request from user gesture. Use tag to replace/update existing notifications. Service Worker notifications support action buttons. Always handle permission denial gracefully.
Warning: Must request permission from user gesture (click). Permission is per-origin. If denied, cannot request again - user must manually enable. Don't spam notifications. Respect requireInteraction - don't force persistent notifications. Test across browsers - behavior varies.
14.2 Push Messaging Registration and Handling
Method/Property
Description
Browser Support
registration.pushManager.subscribe(options)
Subscribes to push notifications. Returns PushSubscription with endpoint and keys.
All Modern Browsers
registration.pushManager.getSubscription()
Gets existing push subscription or null if not subscribed.
// Subscribe to push notificationsasync function subscribeToPush() { try { // Check support if (!("serviceWorker" in navigator) || !("PushManager" in window)) { throw new Error("Push notifications not supported"); } // Register service worker const registration = await navigator.serviceWorker.register("/sw.js"); await navigator.serviceWorker.ready; // Check existing subscription let subscription = await registration.pushManager.getSubscription(); if (subscription) { console.log("Already subscribed:", subscription); return subscription; } // Request notification permission const permission = await Notification.requestPermission(); if (permission !== "granted") { throw new Error("Notification permission denied"); } // Subscribe to push // VAPID public key from your server const vapidPublicKey = "YOUR_VAPID_PUBLIC_KEY"; const convertedVapidKey = urlBase64ToUint8Array(vapidPublicKey); subscription = await registration.pushManager.subscribe({ "userVisibleOnly": true, // Must be true "applicationServerKey": convertedVapidKey }); console.log("Push subscription:", subscription); // Send subscription to server await fetch("/api/push/subscribe", { "method": "POST", "headers": { "Content-Type": "application/json" }, "body": JSON.stringify({ "endpoint": subscription.endpoint, "keys": { "p256dh": arrayBufferToBase64(subscription.getKey("p256dh")), "auth": arrayBufferToBase64(subscription.getKey("auth")) } }) }); console.log("Subscription sent to server"); return subscription; } catch (error) { console.error("Push subscription failed:", error); throw error; }}// Helper: Convert VAPID keyfunction urlBase64ToUint8Array(base64String) { const padding = "=".repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/\-/g, "+") .replace(/_/g, "/"); const rawData = atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray;}// Helper: Convert ArrayBuffer to base64function arrayBufferToBase64(buffer) { const bytes = new Uint8Array(buffer); let binary = ""; for (let i = 0; i < bytes.length; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary);}// Unsubscribeasync function unsubscribeFromPush() { const registration = await navigator.serviceWorker.ready; const subscription = await registration.pushManager.getSubscription(); if (subscription) { await subscription.unsubscribe(); console.log("Unsubscribed from push"); // Notify server await fetch("/api/push/unsubscribe", { "method": "POST", "headers": { "Content-Type": "application/json" }, "body": JSON.stringify({ "endpoint": subscription.endpoint }) }); }}
Example: Handle push messages in service worker
// service-worker.jsself.addEventListener("push", (event) => { console.log("Push received"); let notificationData = { "title": "New Notification", "body": "You have a new update", "icon": "/images/icon-192.png" }; // Parse push data if available if (event.data) { try { notificationData = event.data.json(); } catch (e) { notificationData.body = event.data.text(); } } const options = { "body": notificationData.body, "icon": notificationData.icon || "/images/icon-192.png", "badge": "/images/badge-96.png", "tag": notificationData.tag || "default", "data": notificationData.data || {}, "actions": notificationData.actions || [], "requireInteraction": notificationData.requireInteraction || false, "vibrate": [200, 100, 200] }; // Show notification event.waitUntil( self.registration.showNotification(notificationData.title, options) );});// Handle notification clickself.addEventListener("notificationclick", (event) => { event.notification.close(); const urlToOpen = event.notification.data.url || "/"; event.waitUntil( clients.matchAll({ "type": "window", "includeUncontrolled": true }).then((clientList) => { // Check if already open for (const client of clientList) { if (client.url === urlToOpen && "focus" in client) { return client.focus(); } } // Open new window if (clients.openWindow) { return clients.openWindow(urlToOpen); } }) );});// Handle push subscription changeself.addEventListener("pushsubscriptionchange", (event) => { console.log("Push subscription changed"); event.waitUntil( // Resubscribe self.registration.pushManager.subscribe({ "userVisibleOnly": true, "applicationServerKey": urlBase64ToUint8Array(VAPID_PUBLIC_KEY) }).then((subscription) => { // Send new subscription to server return fetch("/api/push/subscribe", { "method": "POST", "headers": { "Content-Type": "application/json" }, "body": JSON.stringify({ "endpoint": subscription.endpoint, "keys": { "p256dh": arrayBufferToBase64(subscription.getKey("p256dh")), "auth": arrayBufferToBase64(subscription.getKey("auth")) } }) }); }) );});
Note: Push API enables server-to-client push notifications. Requires Service Worker and notification permission. Use VAPID keys for authentication. Subscription includes endpoint and encryption keys - send to your server. Server uses Web Push protocol to send notifications.
Warning: Requires HTTPS and Service Worker. Must set userVisibleOnly: true - silent push not allowed. Handle pushsubscriptionchange event - subscriptions can expire. Don't send sensitive data in push payload - encrypt or fetch from server. Test subscription renewal flow.
// Check if Badge API is supportedif ("setAppBadge" in navigator) { console.log("Badge API supported");} else { console.log("Badge API not supported");}// Set badge with countasync function updateBadge(count) { try { if ("setAppBadge" in navigator) { if (count > 0) { await navigator.setAppBadge(count); console.log(`Badge set to ${count}`); } else { await navigator.clearAppBadge(); console.log("Badge cleared"); } } } catch (error) { console.error("Failed to update badge:", error); }}// Set generic badge (no number)async function setGenericBadge() { try { if ("setAppBadge" in navigator) { await navigator.setAppBadge(); console.log("Generic badge set"); } } catch (error) { console.error("Failed to set badge:", error); }}// Clear badgeasync function clearBadge() { try { if ("clearAppBadge" in navigator) { await navigator.clearAppBadge(); console.log("Badge cleared"); } } catch (error) { console.error("Failed to clear badge:", error); }}// Example: Update badge based on unread messageslet unreadCount = 0;function receiveMessage(message) { unreadCount++; updateBadge(unreadCount); displayMessage(message);}function markAsRead() { unreadCount = Math.max(0, unreadCount - 1); updateBadge(unreadCount);}function markAllAsRead() { unreadCount = 0; clearBadge();}
Example: Badge API in Service Worker
// service-worker.js - Update badge on pushself.addEventListener("push", (event) => { const data = event.data.json(); // Show notification const notificationPromise = self.registration.showNotification( data.title, { "body": data.body, "icon": "/images/icon-192.png", "badge": "/images/badge-96.png", "tag": data.tag, "data": data } ); // Update badge const badgePromise = (async () => { if ("setAppBadge" in navigator) { // Get current unread count from data const unreadCount = data.unreadCount || 1; await navigator.setAppBadge(unreadCount); } })(); event.waitUntil( Promise.all([notificationPromise, badgePromise]) );});// Clear badge when notification clickedself.addEventListener("notificationclick", (event) => { event.notification.close(); const clearBadgePromise = (async () => { if ("clearAppBadge" in navigator) { await navigator.clearAppBadge(); } })(); const openWindowPromise = clients.openWindow( event.notification.data.url || "/" ); event.waitUntil( Promise.all([clearBadgePromise, openWindowPromise]) );});
Note: Badge API sets app icon badge - visible on dock/taskbar/home screen. Works for installed PWAs. Set number for count or no argument for generic indicator. Clear when user views content. Limited browser support but good progressive enhancement.
Warning: Only works for installed PWAs - not regular websites. Limited browser support (Chrome, Edge, Safari). Always check feature support before using. Badge persists across sessions - clear appropriately. Don't use for critical notifications - combine with Notification API.
14.4 Wake Lock API for Screen Wake Management
Method/Property
Description
Browser Support
navigator.wakeLock.request(type)
Requests wake lock. Type: "screen". Returns WakeLockSentinel.
Chrome, Edge
wakeLock.release()
Releases wake lock, allowing screen to sleep.
Chrome, Edge
wakeLock.released
Promise that resolves when wake lock is released.
Chrome, Edge
wakeLock.type
Type of wake lock ("screen").
Chrome, Edge
Example: Prevent screen sleep during video playback
// Wake Lock managerclass WakeLockManager { constructor() { this.wakeLock = null; this.isSupported = "wakeLock" in navigator; } async request() { if (!this.isSupported) { console.log("Wake Lock API not supported"); return false; } try { this.wakeLock = await navigator.wakeLock.request("screen"); console.log("Wake lock acquired"); // Listen for release this.wakeLock.addEventListener("release", () => { console.log("Wake lock released"); this.wakeLock = null; }); return true; } catch (error) { console.error("Wake lock request failed:", error); return false; } } async release() { if (this.wakeLock) { await this.wakeLock.release(); this.wakeLock = null; console.log("Wake lock manually released"); } } async reacquire() { // Reacquire if previously active (e.g., after page visibility change) if (this.wakeLock && this.wakeLock.released) { await this.request(); } }}// Video player with wake lockconst wakeLockManager = new WakeLockManager();const video = document.getElementById("myVideo");video.addEventListener("play", async () => { await wakeLockManager.request();});video.addEventListener("pause", async () => { await wakeLockManager.release();});video.addEventListener("ended", async () => { await wakeLockManager.release();});// Reacquire wake lock when page becomes visibledocument.addEventListener("visibilitychange", async () => { if (document.visibilityState === "visible" && !video.paused) { await wakeLockManager.reacquire(); }});// Release on page unloadwindow.addEventListener("beforeunload", async () => { await wakeLockManager.release();});
Example: Recipe app with wake lock
// Keep screen on while cookingclass RecipeWakeLock { constructor() { this.wakeLock = null; this.isActive = false; } async enable() { if ("wakeLock" in navigator) { try { this.wakeLock = await navigator.wakeLock.request("screen"); this.isActive = true; console.log("Screen will stay on during cooking"); // Update UI document.getElementById("wakeLockBtn").textContent = "Turn Off Keep Awake"; document.getElementById("wakeLockStatus").textContent = "Screen will stay on"; // Handle release this.wakeLock.addEventListener("release", () => { console.log("Wake lock released"); this.isActive = false; this.updateUI(); }); } catch (error) { console.error("Failed to enable wake lock:", error); alert("Could not keep screen on. Make sure page is visible."); } } else { alert("Wake Lock not supported in this browser"); } } async disable() { if (this.wakeLock) { await this.wakeLock.release(); this.isActive = false; this.updateUI(); } } async toggle() { if (this.isActive) { await this.disable(); } else { await this.enable(); } } updateUI() { const btn = document.getElementById("wakeLockBtn"); const status = document.getElementById("wakeLockStatus"); if (this.isActive) { btn.textContent = "Turn Off Keep Awake"; status.textContent = "Screen will stay on"; } else { btn.textContent = "Turn On Keep Awake"; status.textContent = "Screen may sleep"; } }}// Initializeconst recipeWakeLock = new RecipeWakeLock();document.getElementById("wakeLockBtn").addEventListener("click", () => { recipeWakeLock.toggle();});// Auto-enable when starting recipedocument.getElementById("startCookingBtn").addEventListener("click", () => { recipeWakeLock.enable(); startRecipeTimer();});
Note: Wake Lock API prevents screen from sleeping. Use for video playback, reading, recipes, presentations, etc. Only "screen" type currently supported. Wake lock auto-releases when page hidden or battery low. Reacquire on visibility change if needed.
Warning: Limited browser support (Chrome, Edge). Requires visible page - released when tab hidden. Can be denied by browser (low battery, user settings). Always handle request failure gracefully. Remember to release when done - drains battery. Respect user's battery.
14.5 Idle Detection API for User Activity Monitoring
Method/Property
Description
Browser Support
new IdleDetector()
Creates idle detector instance.
Chrome, Edge (Experimental)
detector.start(options)
Starts monitoring. Options: threshold (ms) and signal (AbortSignal).
Chrome, Edge (Experimental)
detector.userState
Current user state: "active" or "idle".
Chrome, Edge (Experimental)
detector.screenState
Current screen state: "locked" or "unlocked".
Chrome, Edge (Experimental)
detector.onchange
Event handler for state changes.
Chrome, Edge (Experimental)
Example: Detect user idle state
// Check support and request permissionasync function setupIdleDetection() { // Check support if (!("IdleDetector" in window)) { console.log("Idle Detection API not supported"); return; } // Request permission try { const permission = await IdleDetector.requestPermission(); if (permission !== "granted") { console.log("Idle detection permission denied"); return; } console.log("Idle detection permission granted"); startIdleDetection(); } catch (error) { console.error("Permission request failed:", error); }}// Start idle detectionasync function startIdleDetection() { try { const idleDetector = new IdleDetector(); const controller = new AbortController(); // Listen for state changes idleDetector.addEventListener("change", () => { const userState = idleDetector.userState; const screenState = idleDetector.screenState; console.log(`User: ${userState}, Screen: ${screenState}`); if (userState === "idle") { handleUserIdle(); } else if (userState === "active") { handleUserActive(); } if (screenState === "locked") { handleScreenLocked(); } else if (screenState === "unlocked") { handleScreenUnlocked(); } }); // Start monitoring await idleDetector.start({ "threshold": 60000, // 60 seconds "signal": controller.signal }); console.log("Idle detection started (threshold: 60s)"); // Stop detection after 10 minutes setTimeout(() => { controller.abort(); console.log("Idle detection stopped"); }, 10 * 60 * 1000); } catch (error) { console.error("Idle detection failed:", error); }}function handleUserIdle() { console.log("User is idle"); // Pause background tasks, sync, etc. pauseBackgroundSync(); showIdleMessage();}function handleUserActive() { console.log("User is active"); // Resume activities resumeBackgroundSync(); hideIdleMessage();}function handleScreenLocked() { console.log("Screen locked"); // Pause video, save work, etc. pauseMedia(); autoSaveWork();}function handleScreenUnlocked() { console.log("Screen unlocked"); // Resume activities resumeMedia();}// InitializesetupIdleDetection();
Note: Idle Detection API monitors user activity and screen lock state. Requires permission. Use for auto-logout, pause sync, save battery. Threshold is minimum idle time before detection. Experimental API - limited support.
Warning: Highly experimental - Chrome/Edge only behind flag. Requires permission. Privacy-sensitive - use responsibly. Provide fallback for unsupported browsers. Don't rely solely on this for security - implement server-side session timeout. Test thoroughly before production use.
14.6 Picture-in-Picture API for Video Overlay
Method/Property
Description
Browser Support
video.requestPictureInPicture()
Requests PiP mode for video element. Returns PictureInPictureWindow.
All Modern Browsers
document.exitPictureInPicture()
Exits PiP mode.
All Modern Browsers
document.pictureInPictureElement
Currently active PiP element or null.
All Modern Browsers
document.pictureInPictureEnabled
Boolean indicating if PiP is available.
All Modern Browsers
pipWindow.width
Width of PiP window.
All Modern Browsers
pipWindow.height
Height of PiP window.
All Modern Browsers
Example: Toggle Picture-in-Picture
// Check PiP supportif (!document.pictureInPictureEnabled) { console.log("Picture-in-Picture not supported"); document.getElementById("pipBtn").disabled = true;}// Toggle PiP modeasync function togglePictureInPicture() { const video = document.getElementById("myVideo"); try { // Exit if already in PiP if (document.pictureInPictureElement) { await document.exitPictureInPicture(); console.log("Exited Picture-in-Picture"); return; } // Enter PiP const pipWindow = await video.requestPictureInPicture(); console.log("Entered Picture-in-Picture"); console.log(`PiP window size: ${pipWindow.width}x${pipWindow.height}`); // Listen for resize pipWindow.addEventListener("resize", () => { console.log(`PiP resized: ${pipWindow.width}x${pipWindow.height}`); }); } catch (error) { console.error("PiP failed:", error); if (error.name === "NotAllowedError") { alert("Picture-in-Picture not allowed. Check browser permissions."); } else if (error.name === "InvalidStateError") { alert("Video must be playing to enable Picture-in-Picture."); } }}// Add button listenerdocument.getElementById("pipBtn").addEventListener("click", togglePictureInPicture);// Listen for PiP eventsconst video = document.getElementById("myVideo");video.addEventListener("enterpictureinpicture", (event) => { console.log("Entered PiP"); document.getElementById("pipBtn").textContent = "Exit PiP"; // Update UI document.body.classList.add("pip-active");});video.addEventListener("leavepictureinpicture", (event) => { console.log("Left PiP"); document.getElementById("pipBtn").textContent = "Enter PiP"; // Update UI document.body.classList.remove("pip-active");});
Example: Auto PiP on scroll or tab switch
// Auto-enable PiP when video scrolls out of view or tab hiddenclass AutoPiPManager { constructor(video) { this.video = video; this.autoPiPEnabled = true; this.observer = null; this.setupIntersectionObserver(); this.setupVisibilityChange(); } setupIntersectionObserver() { // Detect when video scrolls out of view this.observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (!this.autoPiPEnabled) return; // Video scrolled out of view and is playing if (!entry.isIntersecting && !this.video.paused) { this.enterPiP(); } // Video back in view - exit PiP else if (entry.isIntersecting && document.pictureInPictureElement) { this.exitPiP(); } }); }, { "threshold": 0.5 // 50% visibility threshold }); this.observer.observe(this.video); } setupVisibilityChange() { // Enable PiP when switching tabs document.addEventListener("visibilitychange", () => { if (!this.autoPiPEnabled) return; if (document.hidden && !this.video.paused) { this.enterPiP(); } else if (!document.hidden && document.pictureInPictureElement) { this.exitPiP(); } }); } async enterPiP() { if (document.pictureInPictureElement) return; try { await this.video.requestPictureInPicture(); console.log("Auto PiP enabled"); } catch (error) { console.error("Auto PiP failed:", error); } } async exitPiP() { if (!document.pictureInPictureElement) return; try { await document.exitPictureInPicture(); console.log("Auto PiP disabled"); } catch (error) { console.error("Exit PiP failed:", error); } } setAutoPiP(enabled) { this.autoPiPEnabled = enabled; console.log(`Auto PiP ${enabled ? "enabled" : "disabled"}`); // Exit PiP if disabled if (!enabled && document.pictureInPictureElement) { this.exitPiP(); } } destroy() { if (this.observer) { this.observer.disconnect(); } }}// Initializeconst video = document.getElementById("myVideo");const autoPiP = new AutoPiPManager(video);// Toggle auto PiPdocument.getElementById("autoPiPToggle").addEventListener("change", (event) => { autoPiP.setAutoPiP(event.target.checked);});
Example: Custom PiP controls
// Add custom controls to PiP window (Chrome 116+)const video = document.getElementById("myVideo");// Check if Media Session API is available for controlsif ("mediaSession" in navigator) { // Set metadata navigator.mediaSession.metadata = new MediaMetadata({ "title": "My Video Title", "artist": "Content Creator", "album": "Video Series", "artwork": [ { "src": "/images/artwork-96.png", "sizes": "96x96", "type": "image/png" }, { "src": "/images/artwork-512.png", "sizes": "512x512", "type": "image/png" } ] }); // Set action handlers (visible in PiP) navigator.mediaSession.setActionHandler("play", () => { video.play(); }); navigator.mediaSession.setActionHandler("pause", () => { video.pause(); }); navigator.mediaSession.setActionHandler("previoustrack", () => { playPreviousVideo(); }); navigator.mediaSession.setActionHandler("nexttrack", () => { playNextVideo(); }); navigator.mediaSession.setActionHandler("seekbackward", (details) => { video.currentTime = Math.max(video.currentTime - (details.seekOffset || 10), 0); }); navigator.mediaSession.setActionHandler("seekforward", (details) => { video.currentTime = Math.min( video.currentTime + (details.seekOffset || 10), video.duration ); });}// Update playback statevideo.addEventListener("play", () => { navigator.mediaSession.playbackState = "playing";});video.addEventListener("pause", () => { navigator.mediaSession.playbackState = "paused";});// Update positionvideo.addEventListener("timeupdate", () => { if ("setPositionState" in navigator.mediaSession) { navigator.mediaSession.setPositionState({ "duration": video.duration, "playbackRate": video.playbackRate, "position": video.currentTime }); }});
Note: Picture-in-Picture API enables floating video overlay. Video continues playing while user browses other tabs/apps. Must be triggered by user interaction. Use for video conferencing, tutorials, live streams. Combine with Media Session API for custom controls in PiP window.
Warning: Requires user interaction to trigger (security). Some browsers require video to be playing. Can be disabled by browser or user preferences. Always check document.pictureInPictureEnabled. Handle enter/leave events for UI updates. Only one PiP window at a time.
Notification and Messaging Best Practices
Always request notification permission from user gesture - explain why first
Use notification tag to replace/update instead of spamming
Push notifications require Service Worker and VAPID keys
Handle pushsubscriptionchange - subscriptions can expire
Badge API only works for installed PWAs - check support
Clear app badge when user views content - don't leave stale counts
Wake Lock API requires visible page - reacquire after visibility change
Release wake lock when done - respects user's battery
Idle Detection API is experimental - provide fallback detection
Use idle detection for auto-logout, pause sync, save battery
PiP requires user interaction to trigger - can't auto-enable on page load
Combine PiP with Media Session API for rich controls
Handle all notification/PiP events for proper UI state management
Test permission flows - handle grant, deny, and "ask later" states
15. Modern Web Platform APIs
15.1 Web Streams API for Streaming Data Processing
Stream Type
Description
Browser Support
ReadableStream
Stream of data that can be read chunk by chunk. Used for response bodies, file reading, etc.
All Modern Browsers
WritableStream
Stream that can be written to chunk by chunk. Used for file writing, compression, etc.
All Modern Browsers
TransformStream
Pair of readable and writable streams that transform data. Used for compression, encryption, etc.
All Modern Browsers
ReadableStream Method
Description
stream.getReader()
Gets ReadableStreamDefaultReader for reading chunks.
reader.read()
Returns Promise with {value, done}. Done is true when stream ends.
stream.pipeThrough(transform)
Pipes stream through TransformStream, returning new ReadableStream.
stream.pipeTo(writable)
Pipes stream to WritableStream. Returns Promise.
stream.tee()
Splits stream into two independent streams.
reader.cancel(reason)
Cancels stream reading.
Example: Read and process fetch response stream
// Fetch and process response as streamasync function fetchAndProcessStream(url) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } // Get readable stream from response const reader = response.body.getReader(); // Process chunks as they arrive let receivedLength = 0; const chunks = []; while (true) { const { "done": done, "value": value } = await reader.read(); if (done) { console.log("Stream complete"); break; } chunks.push(value); receivedLength += value.length; // Update progress console.log(`Received ${receivedLength} bytes`); updateProgressBar(receivedLength); } // Concatenate chunks const chunksAll = new Uint8Array(receivedLength); let position = 0; for (const chunk of chunks) { chunksAll.set(chunk, position); position += chunk.length; } // Convert to string const result = new TextDecoder("utf-8").decode(chunksAll); console.log("Final result:", result); return result; } catch (error) { console.error("Stream processing failed:", error); throw error; }}// UsagefetchAndProcessStream("/api/large-data") .then(data => { console.log("Processing complete"); processData(data); });
Example: Create custom ReadableStream
// Create custom stream that generates datafunction createNumberStream(max) { let current = 0; return new ReadableStream({ start(controller) { console.log("Stream started"); }, pull(controller) { if (current < max) { // Enqueue next value controller.enqueue(current); current++; } else { // Close stream when done controller.close(); } }, cancel(reason) { console.log("Stream cancelled:", reason); } });}// Read from custom streamasync function readNumberStream() { const stream = createNumberStream(10); const reader = stream.getReader(); try { while (true) { const { "done": done, "value": value } = await reader.read(); if (done) { console.log("Stream finished"); break; } console.log("Received number:", value); } } finally { reader.releaseLock(); }}readNumberStream();
Example: Transform stream for data processing
// Create transform stream that uppercases textclass UppercaseTransformStream { constructor() { return new TransformStream({ transform(chunk, controller) { // Convert chunk to uppercase const text = new TextDecoder().decode(chunk); const upper = text.toUpperCase(); const encoded = new TextEncoder().encode(upper); controller.enqueue(encoded); } }); }}// Use transform streamasync function transformFetchResponse(url) { const response = await fetch(url); // Pipe through transform const transformed = response.body .pipeThrough(new UppercaseTransformStream()); // Read result const reader = transformed.getReader(); const chunks = []; while (true) { const { "done": done, "value": value } = await reader.read(); if (done) break; chunks.push(value); } // Combine chunks const combined = new Uint8Array( chunks.reduce((acc, chunk) => acc + chunk.length, 0) ); let position = 0; for (const chunk of chunks) { combined.set(chunk, position); position += chunk.length; } return new TextDecoder().decode(combined);}// Create JSON parsing transformclass JSONParseTransformStream { constructor() { let buffer = ""; return new TransformStream({ transform(chunk, controller) { const text = new TextDecoder().decode(chunk); buffer += text; // Try to parse complete JSON objects const lines = buffer.split("\n"); buffer = lines.pop(); // Keep incomplete line in buffer for (const line of lines) { if (line.trim()) { try { const json = JSON.parse(line); controller.enqueue(json); } catch (e) { console.error("JSON parse error:", e); } } } }, flush(controller) { // Process remaining buffer if (buffer.trim()) { try { const json = JSON.parse(buffer); controller.enqueue(json); } catch (e) { console.error("JSON parse error:", e); } } } }); }}// Use JSON transformasync function streamJSONData(url) { const response = await fetch(url); const reader = response.body .pipeThrough(new JSONParseTransformStream()) .getReader(); while (true) { const { "done": done, "value": value } = await reader.read(); if (done) break; console.log("Parsed JSON object:", value); processJSONObject(value); }}
Note: Streams API enables processing data chunk by chunk without loading entire dataset into memory. Use ReadableStream for reading, WritableStream for writing, TransformStream for transforming. Great for large files, real-time data, progressive rendering. All fetch responses are ReadableStreams.
15.2 Compression Streams API for Data Compression
Class
Description
Browser Support
CompressionStream
TransformStream that compresses data. Format: "gzip", "deflate", or "deflate-raw".
Chrome, Edge, Safari
DecompressionStream
TransformStream that decompresses data. Same formats as compression.
Chrome, Edge, Safari
Example: Compress and decompress data
// Compress string dataasync function compressData(text) { // Convert to stream const blob = new Blob([text]); const stream = blob.stream(); // Compress using gzip const compressedStream = stream.pipeThrough( new CompressionStream("gzip") ); // Convert to Blob const compressedBlob = await new Response(compressedStream).blob(); console.log(`Original size: ${text.length} bytes`); console.log(`Compressed size: ${compressedBlob.size} bytes`); console.log(`Compression ratio: ${(compressedBlob.size / text.length * 100).toFixed(2)}%`); return compressedBlob;}// Decompress dataasync function decompressData(compressedBlob) { // Get stream from blob const stream = compressedBlob.stream(); // Decompress const decompressedStream = stream.pipeThrough( new DecompressionStream("gzip") ); // Convert to text const decompressedBlob = await new Response(decompressedStream).blob(); const text = await decompressedBlob.text(); return text;}// Usageconst originalText = "Lorem ipsum dolor sit amet...".repeat(100);compressData(originalText) .then(compressed => { console.log("Compressed successfully"); return decompressData(compressed); }) .then(decompressed => { console.log("Decompressed successfully"); console.log("Match:", decompressed === originalText); });
// Fetch compressed data and decompress while downloadingasync function fetchAndDecompressStream(url) { try { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error: ${response.status}`); } // Check if content is compressed const contentEncoding = response.headers.get("Content-Encoding"); console.log("Content-Encoding:", contentEncoding); // Decompress stream const decompressedStream = response.body .pipeThrough(new DecompressionStream("gzip")); // Process decompressed data const reader = decompressedStream.getReader(); const decoder = new TextDecoder(); let result = ""; while (true) { const { "done": done, "value": value } = await reader.read(); if (done) { console.log("Decompression complete"); break; } // Decode chunk const chunk = decoder.decode(value, { "stream": true }); result += chunk; // Process chunk progressively console.log(`Processed ${result.length} characters`); } return result; } catch (error) { console.error("Fetch and decompress failed:", error); throw error; }}// UsagefetchAndDecompressStream("/api/data.gz") .then(data => { console.log("Data loaded:", data.substring(0, 100)); processData(data); });
Note: Compression Streams API provides built-in compression/decompression. Supports gzip, deflate, and deflate-raw. Use for reducing upload/download sizes, compressing cached data, file compression. Works with any stream - files, fetch responses, etc. No external libraries needed.
Warning: Limited browser support - check before using. Compression is CPU-intensive - may block on large data. Consider Web Workers for large compressions. Server must support receiving compressed data. Always measure - small data may not benefit from compression overhead.
15.3 Web Locks API for Resource Synchronization
Method/Property
Description
Browser Support
navigator.locks.request(name, callback)
Requests lock with name. Callback executes when lock acquired. Returns Promise.
Chrome, Edge
navigator.locks.request(name, options, callback)
Request with options: mode ("exclusive"/"shared"), ifAvailable, steal, signal.
Chrome, Edge
navigator.locks.query()
Returns Promise with current lock state (held, pending locks).
Chrome, Edge
Lock Option
Type
Description
mode
string
"exclusive" (default) or "shared". Exclusive allows one holder, shared allows multiple.
ifAvailable
boolean
If true, callback runs only if lock immediately available. Otherwise callback receives null.
steal
boolean
If true, preempts existing lock. Dangerous - use with caution.
signal
AbortSignal
AbortSignal to cancel lock request.
Example: Exclusive lock for critical section
// Protect critical section with exclusive lockasync function updateSharedResource(data) { if (!("locks" in navigator)) { console.log("Web Locks API not supported"); return updateSharedResourceFallback(data); } try { await navigator.locks.request("resource-lock", async (lock) => { console.log("Lock acquired"); // Critical section - only one execution at a time const current = await fetchCurrentData(); const updated = processData(current, data); await saveData(updated); console.log("Resource updated successfully"); // Lock released when callback completes }); console.log("Lock released"); } catch (error) { console.error("Lock request failed:", error); throw error; }}// Multiple concurrent calls will be serializedPromise.all([ updateSharedResource({ "id": 1 }), updateSharedResource({ "id": 2 }), updateSharedResource({ "id": 3 })]).then(() => { console.log("All updates complete");});
Note: Web Locks API provides cross-tab/worker resource synchronization. Use exclusive locks for writes, shared locks for reads. Lock held until callback Promise resolves. Prevents race conditions in IndexedDB, shared state, etc. Works across tabs, windows, workers.
Warning: Limited browser support (Chrome, Edge). Locks are NOT persistent - lost on page refresh. Don't use for long-running operations - blocks other contexts. Always use timeout with AbortController. Avoid deadlocks - don't request multiple locks without careful ordering. Test cross-tab scenarios thoroughly.
15.4 Scheduler API (postTask) for Task Prioritization
Method/Property
Description
Browser Support
scheduler.postTask(callback, options)
Schedules task with priority. Returns Promise that resolves with callback result.
Chrome, Edge
scheduler.yield()
Yields to browser for higher priority work. Returns Promise. EXPERIMENTAL
Experimental
Priority Option
Description
"user-blocking"
Highest priority. Blocks user interaction. Use for input response, animations.
"user-visible"
Default priority. User-visible work. Use for rendering, updating UI.
"background"
Lowest priority. Background work. Use for analytics, preloading, cleanup.
Example: Prioritize tasks with scheduler.postTask
// Check supportif ("scheduler" in window && "postTask" in scheduler) { console.log("Scheduler API supported");} else { console.log("Scheduler API not supported - using fallback");}// Schedule tasks with different prioritiesasync function schedulePrioritizedTasks() { // High priority - user interaction response scheduler.postTask(() => { console.log("1. User-blocking task (high priority)"); handleUserInput(); }, { "priority": "user-blocking" }); // Medium priority - UI update scheduler.postTask(() => { console.log("2. User-visible task (medium priority)"); updateUI(); }, { "priority": "user-visible" }); // Low priority - background work scheduler.postTask(() => { console.log("3. Background task (low priority)"); sendAnalytics(); }, { "priority": "background" }); // Tasks execute in priority order, not call order}schedulePrioritizedTasks();
Example: Break up long task with yields
// Process large dataset with yields to prevent blockingasync function processLargeDataset(items) { const results = []; for (let i = 0; i < items.length; i++) { // Process item const result = await processItem(items[i]); results.push(result); // Yield every 10 items to prevent blocking if (i % 10 === 0 && "scheduler" in window) { await scheduler.yield(); console.log(`Processed ${i} items, yielding...`); } } return results;}// Fallback using setTimeoutasync function yieldToMainThread() { return new Promise(resolve => setTimeout(resolve, 0));}// Universal yield functionasync function smartYield() { if ("scheduler" in window && "yield" in scheduler) { await scheduler.yield(); } else { await yieldToMainThread(); }}// UsageprocessLargeDataset(largeArray) .then(results => { console.log("All items processed"); displayResults(results); });
Note: Scheduler API enables task prioritization for better responsiveness. Use "user-blocking" for input handlers, "user-visible" for UI updates, "background" for analytics. scheduler.yield() allows long tasks to break up and yield to browser. Better than setTimeout(0) for task scheduling.
Warning: Limited browser support (Chrome, Edge). scheduler.yield() is experimental. Provide fallback using setTimeout. Priorities are hints - browser controls actual scheduling. Don't overuse user-blocking - can hurt performance. Test with Performance Observer for long tasks.
15.5 View Transitions API for Smooth Page Transitions
Note: View Transitions API enables smooth animated transitions between DOM states. Browser captures before/after states and animates between them. Use view-transition-name CSS property for element-specific transitions. Great for SPA navigation, theme switches, list-to-detail transitions.
Warning: Limited browser support (Chrome, Edge). Always provide fallback. Transitions are skipped if callback throws error. Don't use for time-critical updates. Large DOM changes can be expensive to capture. Test performance on lower-end devices. CSS animations control transition appearance.
15.6 Container Queries API Integration
CSS Property
Description
Browser Support
container-type
Defines element as query container. Values: size, inline-size, normal.
All Modern Browsers
container-name
Names container for targeting in queries.
All Modern Browsers
container
Shorthand: container: name / type.
All Modern Browsers
@container
Container query rule. Syntax: @container name (condition) { ... }.
<!-- HTML --><div class="grid"> <div class="grid-item"> <div class="component-container"> <div class="component"> <h2>Responsive Component</h2> <p>This component adapts to its container size, not viewport.</p> </div> </div> </div> <!-- More grid items... --></div>
Example: Container queries CSS for responsive component
/* Setup grid that changes columns */.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 1rem;}/* Each item is a container */.component-container { container-type: inline-size; container-name: component; border: 1px solid #ccc; border-radius: 8px; overflow: hidden;}/* Component adapts to its container */.component { padding: 1rem;}/* Small container */@container component (max-width: 349px) { .component h2 { font-size: 1.2rem; } .component p { font-size: 0.9rem; } .component-layout { display: block; }}/* Medium container */@container component (min-width: 350px) and (max-width: 599px) { .component h2 { font-size: 1.5rem; } .component p { font-size: 1rem; } .component-layout { display: flex; gap: 1rem; }}/* Large container */@container component (min-width: 600px) { .component h2 { font-size: 2rem; } .component p { font-size: 1.1rem; line-height: 1.6; } .component-layout { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; }}/* Container query units */.component-title { /* cqw: % of container width */ /* cqh: % of container height */ /* cqi: % of container inline size */ /* cqb: % of container block size */ /* cqmin: min of cqi and cqb */ /* cqmax: max of cqi and cqb */ font-size: clamp(1rem, 4cqw, 2.5rem); margin-bottom: 1cqh;}
Note: Container Queries allow components to respond to their container size, not viewport. More flexible than media queries for reusable components. Use container-type: inline-size for width-based queries. Container query units (cqw, cqh) relative to container, not viewport.
Warning: Container queries are CSS feature, not JavaScript API. Use CSS.supports() to check support. Avoid circular dependencies (container size depends on children, children depend on container). Performance impact on complex layouts - test on lower-end devices. Fallback to media queries for older browsers.
Modern Web Platform APIs Best Practices
Streams API perfect for large files and real-time data processing
Use ReadableStream.getReader() to process chunks incrementally
Compression Streams reduce bandwidth - measure to ensure benefit
Web Locks API prevents race conditions across tabs/workers
Use shared locks for reads, exclusive locks for writes
Scheduler.postTask prioritizes tasks better than setTimeout
Use "user-blocking" sparingly - only for critical interactions
Yield with scheduler.yield() to prevent blocking main thread
View Transitions API smooths SPA navigation and state changes
Always provide fallback for View Transitions - limited support
Container queries make truly reusable responsive components
Check API support with CSS.supports() and feature detection
Test all modern APIs on target browsers - support varies
16. Internationalization and Formatting APIs
16.1 Intl Object and Localization Services
Intl Class
Purpose
Browser Support
Intl.DateTimeFormat
Locale-aware date and time formatting.
All Browsers
Intl.NumberFormat
Locale-aware number formatting (currency, percent, units).
All Browsers
Intl.Collator
Locale-aware string comparison and sorting.
All Browsers
Intl.PluralRules
Locale-aware pluralization rules.
All Modern Browsers
Intl.RelativeTimeFormat
Locale-aware relative time formatting ("2 days ago").
All Modern Browsers
Intl.ListFormat
Locale-aware list formatting ("A, B, and C").
All Modern Browsers
Intl.DisplayNames
Locale-aware display names for languages, regions, scripts.
All Modern Browsers
Intl.Locale
Locale identifier parsing and manipulation.
All Modern Browsers
Common Method
Description
Intl.getCanonicalLocales(locales)
Returns canonical locale identifiers.
Intl.supportedValuesOf(key)
Returns supported values for key: "calendar", "currency", "timeZone", etc.
Example: Check supported locales and values
// Get canonical locale identifiersconst locales = Intl.getCanonicalLocales(["EN-us", "en-GB", "ja-JP"]);console.log("Canonical locales:", locales);// Output: ["en-US", "en-GB", "ja-JP"]// Check supported currenciesif (Intl.supportedValuesOf) { const currencies = Intl.supportedValuesOf("currency"); console.log("Supported currencies:", currencies.slice(0, 10)); // Output: ["AED", "AFN", "ALL", "AMD", "ANG", ...] // Check if specific currency is supported const hasEUR = currencies.includes("EUR"); console.log("EUR supported:", hasEUR); // Get all time zones const timeZones = Intl.supportedValuesOf("timeZone"); console.log("Time zones count:", timeZones.length); console.log("Sample time zones:", timeZones.slice(0, 5)); // Output: ["Africa/Abidjan", "Africa/Accra", ...] // Get all calendars const calendars = Intl.supportedValuesOf("calendar"); console.log("Supported calendars:", calendars); // Output: ["buddhist", "chinese", "gregory", "hebrew", "indian", ...] // Get all numbering systems const numberingSystems = Intl.supportedValuesOf("numberingSystem"); console.log("Numbering systems:", numberingSystems.slice(0, 10)); // Output: ["arab", "arabext", "bali", "beng", ...]}// Detect user's localeconst userLocale = navigator.language || navigator.userLanguage;console.log("User locale:", userLocale);// Output: "en-US" (or user's system locale)// Get all preferred localesconst userLocales = navigator.languages;console.log("User locales:", userLocales);// Output: ["en-US", "en", "es"]
Note: Intl object provides locale-aware formatting and parsing for dates, numbers, strings, and more. Uses Unicode CLDR data. Always specify locale or use user's locale from navigator.language. All formatters accept locale and options.
16.2 Intl.DateTimeFormat for Date Localization
Option
Values
Description
dateStyle
"full", "long", "medium", "short"
Predefined date format style. Cannot combine with individual date/time options.
timeStyle
"full", "long", "medium", "short"
Predefined time format style.
year
"numeric", "2-digit"
Year representation.
month
"numeric", "2-digit", "long", "short", "narrow"
Month representation.
day
"numeric", "2-digit"
Day representation.
weekday
"long", "short", "narrow"
Weekday representation.
hour
"numeric", "2-digit"
Hour representation.
minute
"numeric", "2-digit"
Minute representation.
second
"numeric", "2-digit"
Second representation.
timeZone
IANA time zone name
Time zone: "America/New_York", "Europe/London", "UTC", etc.
hour12
true, false
Use 12-hour or 24-hour time.
Example: Format dates in different locales
const date = new Date("2024-03-15T14:30:00");// Basic formatting with dateStyle and timeStyleconst formatterUS = new Intl.DateTimeFormat("en-US", { "dateStyle": "full", "timeStyle": "long"});console.log("en-US:", formatterUS.format(date));// Output: "Friday, March 15, 2024 at 2:30:00 PM"const formatterFR = new Intl.DateTimeFormat("fr-FR", { "dateStyle": "full", "timeStyle": "long"});console.log("fr-FR:", formatterFR.format(date));// Output: "vendredi 15 mars 2024 à 14:30:00"const formatterJA = new Intl.DateTimeFormat("ja-JP", { "dateStyle": "full", "timeStyle": "long"});console.log("ja-JP:", formatterJA.format(date));// Output: "2024年3月15日金曜日 14:30:00"// Custom formattingconst customFormatter = new Intl.DateTimeFormat("en-US", { "year": "numeric", "month": "long", "day": "numeric", "weekday": "long", "hour": "numeric", "minute": "2-digit", "hour12": true});console.log("Custom:", customFormatter.format(date));// Output: "Friday, March 15, 2024, 2:30 PM"// Different stylesconst styles = ["full", "long", "medium", "short"];styles.forEach(style => { const formatter = new Intl.DateTimeFormat("en-US", { "dateStyle": style }); console.log(`${style}:`, formatter.format(date));});// Output:// full: Friday, March 15, 2024// long: March 15, 2024// medium: Mar 15, 2024// short: 3/15/24
Example: Time zones and formatting parts
const date = new Date("2024-03-15T14:30:00Z");// Format in different time zonesconst timeZones = [ "America/New_York", "Europe/London", "Asia/Tokyo", "Australia/Sydney"];timeZones.forEach(timeZone => { const formatter = new Intl.DateTimeFormat("en-US", { "timeZone": timeZone, "dateStyle": "medium", "timeStyle": "long", "timeZoneName": "short" }); console.log(`${timeZone}:`, formatter.format(date));});// Output:// America/New_York: Mar 15, 2024, 10:30:00 AM EDT// Europe/London: Mar 15, 2024, 2:30:00 PM GMT// Asia/Tokyo: Mar 15, 2024, 11:30:00 PM JST// Australia/Sydney: Mar 16, 2024, 1:30:00 AM AEDT// Get formatted parts for custom renderingconst formatter = new Intl.DateTimeFormat("en-US", { "year": "numeric", "month": "long", "day": "numeric", "hour": "numeric", "minute": "2-digit"});const parts = formatter.formatToParts(date);console.log("Parts:", parts);// Output: [// { type: "month", value: "March" },// { type: "literal", value: " " },// { type: "day", value: "15" },// { type: "literal", value: ", " },// { type: "year", value: "2024" },// ...// ]// Build custom format from partsconst customFormat = parts.map(part => { if (part.type === "month") return `<strong>${part.value}</strong>`; if (part.type === "day") return `<span class="day">${part.value}</span>`; return part.value;}).join("");console.log("Custom HTML:", customFormat);// Format rangeconst startDate = new Date("2024-03-15");const endDate = new Date("2024-03-20");const range = formatter.formatRange(startDate, endDate);console.log("Range:", range);// Output: "March 15 – 20, 2024"
Note: Intl.DateTimeFormat provides locale-aware date/time formatting. Use dateStyle/timeStyle for quick formatting or individual options for custom formats. formatToParts() returns parts for custom rendering. formatRange() formats date ranges intelligently.
16.3 Intl.NumberFormat for Number Localization
Option
Values
Description
style
"decimal", "currency", "percent", "unit"
Number formatting style.
currency
ISO 4217 currency code
Currency: "USD", "EUR", "JPY", etc. Required if style is "currency".
currencyDisplay
"symbol", "code", "name", "narrowSymbol"
How to display currency.
unit
Unit identifier
Unit: "kilometer", "celsius", "byte", etc. Required if style is "unit".
Note: Intl.NumberFormat handles numbers, currency, percentages, and units. Use style option to choose format type. Compact notation great for large numbers. formatToParts() available for custom rendering. Automatically handles locale-specific grouping and decimals.
Note: Intl.Collator provides locale-aware string comparison for sorting and searching. Use compare method with Array.sort(). Set numeric: true for natural sorting of numbers in strings. Different sensitivity levels for search scenarios.
Note: Intl.PluralRules provides locale-specific plural categories. Different languages have different plural rules (English has 2, Russian has 4, Arabic has 6). Use select() to get category, then map to appropriate message. Use "ordinal" type for 1st, 2nd, 3rd formatting.
16.6 Intl.Locale for Language Tag Parsing
Property
Description
locale.language
Language subtag (e.g., "en", "fr", "zh").
locale.region
Region subtag (e.g., "US", "GB", "CN").
locale.script
Script subtag (e.g., "Latn", "Cyrl", "Hans").
locale.baseName
Base locale without extensions (e.g., "en-US").
locale.calendar
Calendar system (e.g., "gregory", "buddhist", "chinese").
locale.numberingSystem
Numbering system (e.g., "latn", "arab", "hanidec").
Example: Locale-specific calendar and numbering systems
// Use locale with different calendarconst gregorianLocale = new Intl.Locale("en-US", { "calendar": "gregory"});const buddhistLocale = new Intl.Locale("th-TH", { "calendar": "buddhist"});const date = new Date("2024-03-15");const gregorianFormatter = new Intl.DateTimeFormat(gregorianLocale, { "year": "numeric", "month": "long", "day": "numeric"});console.log("Gregorian:", gregorianFormatter.format(date));// Output: "March 15, 2024"const buddhistFormatter = new Intl.DateTimeFormat(buddhistLocale, { "year": "numeric", "month": "long", "day": "numeric"});console.log("Buddhist:", buddhistFormatter.format(date));// Output: "15 มีนาคม 2567" (year 2567 in Buddhist calendar)// Different numbering systemsconst arabicLocale = new Intl.Locale("ar-EG", { "numberingSystem": "arab"});const westernArabicLocale = new Intl.Locale("ar-EG", { "numberingSystem": "latn"});const number = 12345;const arabicFormatter = new Intl.NumberFormat(arabicLocale);console.log("Arabic digits:", arabicFormatter.format(number));// Output: "١٢٬٣٤٥"const latinFormatter = new Intl.NumberFormat(westernArabicLocale);console.log("Latin digits:", latinFormatter.format(number));// Output: "12,345"// Get locale infoconsole.log("\nLocale info:");console.log("Calendars:", Intl.supportedValuesOf("calendar"));console.log("Numbering systems:", Intl.supportedValuesOf("numberingSystem"));
Note: Intl.Locale parses and manipulates BCP 47 language tags. Use to extract language, region, script, and extensions. maximize() adds likely subtags, minimize() removes redundant ones. Can specify calendar, numbering system, hour cycle in locale.
Warning: Locale strings must be valid BCP 47 tags - invalid tags throw errors. Not all calendar/numbering systems supported in all browsers. Use Intl.supportedValuesOf() to check available values. maximize() and minimize() have limited browser support - check before using.
Internationalization and Formatting Best Practices
Always use Intl APIs for locale-sensitive formatting - don't build your own
Get user's locale from navigator.language or navigator.languages
Use Intl.supportedValuesOf() to check available currencies, time zones, etc.
DateTimeFormat: Use dateStyle/timeStyle for quick formatting
NumberFormat: Always specify currency with style: "currency"
Use compact notation for large numbers in UI (1.2B instead of 1,234,567,890)
Collator: Enable numeric: true for natural sorting of filenames
PluralRules: Build locale-aware pluralization - languages have different rules
Cache Intl formatters - creating formatters is expensive
Use formatToParts() when you need fine-grained control over rendering
Test with multiple locales - especially RTL languages (ar, he)
Consider time zones - use IANA time zone identifiers
Provide fallback locale if user's locale not supported
17. Progressive Web App (PWA) APIs
17.1 Web App Manifest API for App Installation
Manifest Property
Description
Example
name
Full name of the app displayed during installation.
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Task Manager Pro</title> <!-- Link to Web App Manifest --> <link rel="manifest" href="/manifest.json"> <!-- Theme color for browser chrome --> <meta name="theme-color" content="#2196f3"> <!-- Apple-specific tags --> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="default"> <meta name="apple-mobile-web-app-title" content="TaskPro"> <link rel="apple-touch-icon" href="/icons/icon-192x192.png"></head><body> <h1>Task Manager Pro</h1> <script> // Check if manifest is linked if ('manifest' in document.createElement('link')) { console.log("Web App Manifest supported"); } // Get manifest programmatically (limited support) if ('getManifest' in document) { document.getManifest().then(manifest => { console.log("Manifest:", manifest); }); } </script></body></html>
Note: Web App Manifest is a JSON file defining PWA metadata. Link with <link rel="manifest">. Icons should include multiple sizes (72px to 512px). Use "purpose": "maskable" for adaptive icons. Screenshots shown in install prompts on some platforms.
17.2 App Installation Prompts and beforeinstallprompt
Event/Method
Description
beforeinstallprompt
Event fired when browser is ready to show install prompt.
event.prompt()
Shows the install prompt (must be called from user gesture).
event.userChoice
Promise resolving to user's choice: "accepted" or "dismissed".
appinstalled
Event fired when app is successfully installed.
Install Criteria (Chrome/Edge)
Required
HTTPS
✓ Site must be served over HTTPS (or localhost)
Web App Manifest
✓ Valid manifest with name, icons, start_url, display
Service Worker
✓ Registered service worker with fetch event handler
User Engagement
✓ User has visited site at least once
Example: Custom install button with beforeinstallprompt
// Store the install prompt eventlet deferredPrompt;const installButton = document.getElementById("install-button");// Hide install button initiallyinstallButton.style.display = "none";// Listen for beforeinstallprompt eventwindow.addEventListener("beforeinstallprompt", (event) => { console.log("beforeinstallprompt fired"); // Prevent the default browser install prompt event.preventDefault(); // Store the event for later use deferredPrompt = event; // Show custom install button installButton.style.display = "block"; // Track that prompt is available analytics.track("install_prompt_available");});// Handle custom install button clickinstallButton.addEventListener("click", async () => { if (!deferredPrompt) { console.log("Install prompt not available"); return; } // Hide the install button installButton.style.display = "none"; // Show the install prompt deferredPrompt.prompt(); // Wait for user's choice const choiceResult = await deferredPrompt.userChoice; console.log(`User choice: ${choiceResult.outcome}`); if (choiceResult.outcome === "accepted") { console.log("User accepted the install prompt"); analytics.track("install_accepted"); } else { console.log("User dismissed the install prompt"); analytics.track("install_dismissed"); // Show button again after dismissal (optional) // installButton.style.display = "block"; } // Clear the deferred prompt deferredPrompt = null;});// Listen for successful installationwindow.addEventListener("appinstalled", (event) => { console.log("PWA installed successfully"); // Hide install button installButton.style.display = "none"; // Clear deferred prompt deferredPrompt = null; // Track installation analytics.track("app_installed"); // Show thank you message showNotification("Thanks for installing our app!");});
Example: Check if already installed and detection
// Check if app is already installed (display mode detection)function isInstalled() { // Check if running in standalone mode if (window.matchMedia("(display-mode: standalone)").matches) { return true; } // Check Safari-specific property if (window.navigator.standalone === true) { return true; } return false;}// Use on page loadif (isInstalled()) { console.log("App is installed and running in standalone mode"); // Hide install button permanently document.getElementById("install-button").style.display = "none"; // Show app-specific features showInstalledUserFeatures();} else { console.log("App is running in browser"); // Show install promotion showInstallPromotion();}// Listen for display mode changesconst displayModeMediaQuery = window.matchMedia("(display-mode: standalone)");displayModeMediaQuery.addEventListener("change", (event) => { if (event.matches) { console.log("Switched to standalone mode"); } else { console.log("Switched to browser mode"); }});// Smart install prompt timingclass InstallPromptManager { constructor() { this.promptShown = localStorage.getItem("install_prompt_shown") === "true"; this.installDismissed = localStorage.getItem("install_dismissed") === "true"; this.visitCount = parseInt(localStorage.getItem("visit_count") || "0"); } incrementVisit() { this.visitCount++; localStorage.setItem("visit_count", this.visitCount.toString()); } shouldShowPrompt() { // Don't show if already shown or dismissed if (this.promptShown || this.installDismissed) { return false; } // Show after 3 visits if (this.visitCount < 3) { return false; } // Add more custom logic (e.g., time on site, engagement) return true; } markPromptShown() { this.promptShown = true; localStorage.setItem("install_prompt_shown", "true"); } markDismissed() { this.installDismissed = true; localStorage.setItem("install_dismissed", "true"); }}// Usageconst promptManager = new InstallPromptManager();promptManager.incrementVisit();window.addEventListener("beforeinstallprompt", (event) => { event.preventDefault(); deferredPrompt = event; if (promptManager.shouldShowPrompt()) { // Show install UI showInstallUI(); promptManager.markPromptShown(); }});
Note:beforeinstallprompt allows custom install UX. Call event.preventDefault() to prevent default prompt. Store event and call prompt() from user gesture. Check userChoice promise for outcome. Not available on iOS Safari - use custom install instructions instead.
Warning:beforeinstallprompt only available on Chrome, Edge, and Android browsers. Not supported on iOS/Safari. Install criteria vary by browser. Prompt() can only be called once per event. Must be called from user gesture (click, touch).
17.3 Window Controls Overlay for Desktop PWAs
Feature
Description
Window Controls Overlay
Allows PWA to use title bar area for custom content on desktop.
// Check if Window Controls Overlay is availableif ("windowControlsOverlay" in navigator) { const windowControls = navigator.windowControlsOverlay; console.log("Overlay visible:", windowControls.visible); if (windowControls.visible) { // Get title bar area dimensions const titleBarRect = windowControls.getTitlebarAreaRect(); console.log("Title bar area:", { x: titleBarRect.x, y: titleBarRect.y, width: titleBarRect.width, height: titleBarRect.height }); // Update custom title bar layout updateTitleBarLayout(titleBarRect); } // Listen for geometry changes (resize, maximize, etc.) windowControls.addEventListener("geometrychange", (event) => { console.log("Title bar geometry changed"); const titleBarRect = windowControls.getTitlebarAreaRect(); const isVisible = windowControls.visible; console.log("New dimensions:", titleBarRect); console.log("Visible:", isVisible); // Update layout updateTitleBarLayout(titleBarRect); });}// Update title bar layoutfunction updateTitleBarLayout(rect) { const titleBar = document.getElementById("custom-title-bar"); if (titleBar) { // Position title bar content avoiding system controls titleBar.style.position = "fixed"; titleBar.style.top = `${rect.y}px`; titleBar.style.left = `${rect.x}px`; titleBar.style.width = `${rect.width}px`; titleBar.style.height = `${rect.height}px`; }}// CSS for draggable title bar areaconst style = document.createElement("style");style.textContent = ` #custom-title-bar { app-region: drag; /* Makes area draggable */ display: flex; align-items: center; padding: 0 16px; background: var(--theme-color); color: white; } #custom-title-bar button, #custom-title-bar a, #custom-title-bar input { app-region: no-drag; /* Make interactive elements clickable */ }`;document.head.appendChild(style);
Example: CSS for Window Controls Overlay
/* Environment variables for safe areas */#app-header { /* Use title bar area when available */ padding-top: env(titlebar-area-height, 0px); padding-left: env(titlebar-area-x, 0px); padding-right: env(titlebar-area-width, 100%);}/* Draggable region for custom title bar */.title-bar { -webkit-app-region: drag; app-region: drag; height: env(titlebar-area-height, 40px); display: flex; align-items: center; background: var(--theme-color);}/* Make buttons clickable in drag region */.title-bar button,.title-bar a,.title-bar input,.title-bar select { -webkit-app-region: no-drag; app-region: no-drag;}/* Adjust layout when overlay is active */@media (display-mode: window-controls-overlay) { body { padding-top: 0; } #custom-title-bar { display: flex; }}
Note: Window Controls Overlay enables native-like title bars in desktop PWAs. Set "display_override": ["window-controls-overlay"] in manifest. Use app-region: drag CSS to make areas draggable. Set app-region: no-drag on interactive elements. Experimental - currently only in Chromium-based browsers.
17.4 App Shortcuts API for Context Menu Integration
// Detect if app was opened from shortcutconst urlParams = new URLSearchParams(window.location.search);const source = urlParams.get("source");if (source === "shortcut") { console.log("App opened from shortcut"); // Track shortcut usage const path = window.location.pathname; analytics.track("shortcut_used", { path }); // Handle specific shortcut actions if (path === "/new-task") { // Open new task modal openNewTaskModal(); } else if (path === "/my-tasks") { // Navigate to tasks view navigateToTasks(); } else if (path === "/calendar") { // Navigate to calendar view navigateToCalendar(); }}// Alternative: Use URL hash for shortcut routingwindow.addEventListener("load", () => { const hash = window.location.hash; switch (hash) { case "#new-task": openNewTaskModal(); break; case "#my-tasks": navigateToTasks(); break; case "#calendar": navigateToCalendar(); break; default: // Show default view showDashboard(); }});
Note: App Shortcuts appear in OS context menus (right-click on app icon, taskbar, etc.). Maximum 4 shortcuts recommended. Icons should be 96x96 minimum. URLs can include query parameters to track source. Supported on Windows, macOS, Android.
17.5 Display Mode Detection and Handling
Display Mode
Description
fullscreen
Full screen without any browser UI.
standalone
Standalone app without browser chrome (recommended for PWAs).
minimal-ui
Standalone with minimal browser UI (back/forward buttons).
browser
Regular browser tab.
window-controls-overlay
Desktop PWA with custom title bar.
Example: Detect and respond to display mode
// Check current display modefunction getDisplayMode() { // Check each display mode const modes = [ "fullscreen", "standalone", "minimal-ui", "browser" ]; for (const mode of modes) { if (window.matchMedia(`(display-mode: ${mode})`).matches) { return mode; } } return "browser"; // Default}// Use on page loadconst displayMode = getDisplayMode();console.log("Display mode:", displayMode);// Apply mode-specific styles or featuresswitch (displayMode) { case "fullscreen": console.log("Running in fullscreen mode"); hideNavigationControls(); break; case "standalone": console.log("Running as installed PWA"); showPWAFeatures(); hideInstallButton(); break; case "minimal-ui": console.log("Running with minimal UI"); adjustLayoutForMinimalUI(); break; case "browser": console.log("Running in browser"); showInstallPrompt(); break;}// Listen for display mode changesconst displayModeQueries = { fullscreen: window.matchMedia("(display-mode: fullscreen)"), standalone: window.matchMedia("(display-mode: standalone)"), minimalUi: window.matchMedia("(display-mode: minimal-ui)"), browser: window.matchMedia("(display-mode: browser)")};Object.keys(displayModeQueries).forEach(mode => { displayModeQueries[mode].addEventListener("change", (event) => { if (event.matches) { console.log(`Display mode changed to: ${mode}`); handleDisplayModeChange(mode); } });});function handleDisplayModeChange(mode) { // Update UI based on new display mode document.body.dataset.displayMode = mode; // Trigger layout recalculation window.dispatchEvent(new Event("displaymodechange"));}
Note: Display mode indicates how PWA is being viewed. Use matchMedia("(display-mode: MODE)") to detect mode. Apply mode-specific features and styles. Common pattern: hide install button in standalone mode, show custom navigation in fullscreen.
17.6 Install Events and App Lifecycle Management
Event
Description
beforeinstallprompt
Fired when browser wants to show install prompt.
appinstalled
Fired when PWA is successfully installed.
DOMContentLoaded
Fired when initial HTML is loaded and parsed.
load
Fired when all resources are loaded.
visibilitychange
Fired when page visibility changes (app backgrounded/foregrounded).
pagehide / pageshow
Fired when page is hidden/shown (mobile app switching).
Example: Complete PWA lifecycle management
class PWALifecycleManager { constructor() { this.isInstalled = this.checkInstalled(); this.installPrompt = null; this.init(); } init() { // Installation events window.addEventListener("beforeinstallprompt", this.handleBeforeInstall.bind(this)); window.addEventListener("appinstalled", this.handleAppInstalled.bind(this)); // Lifecycle events document.addEventListener("visibilitychange", this.handleVisibilityChange.bind(this)); window.addEventListener("pageshow", this.handlePageShow.bind(this)); window.addEventListener("pagehide", this.handlePageHide.bind(this)); // Network events window.addEventListener("online", this.handleOnline.bind(this)); window.addEventListener("offline", this.handleOffline.bind(this)); // Focus events window.addEventListener("focus", this.handleFocus.bind(this)); window.addEventListener("blur", this.handleBlur.bind(this)); } checkInstalled() { // Check if running in standalone mode return window.matchMedia("(display-mode: standalone)").matches || window.navigator.standalone === true; } handleBeforeInstall(event) { console.log("Install prompt available"); event.preventDefault(); this.installPrompt = event; // Show custom install UI this.showInstallUI(); // Track this.trackEvent("install_prompt_shown"); } async showInstallPrompt() { if (!this.installPrompt) { console.log("No install prompt available"); return; } // Show prompt this.installPrompt.prompt(); // Wait for user choice const result = await this.installPrompt.userChoice; if (result.outcome === "accepted") { this.trackEvent("install_accepted"); } else { this.trackEvent("install_dismissed"); } this.installPrompt = null; } handleAppInstalled(event) { console.log("App installed successfully"); this.isInstalled = true; // Hide install UI this.hideInstallUI(); // Track installation this.trackEvent("app_installed"); // Show welcome message this.showWelcomeMessage(); } handleVisibilityChange() { if (document.hidden) { console.log("App went to background"); this.onBackground(); } else { console.log("App came to foreground"); this.onForeground(); } } handlePageShow(event) { console.log("Page shown"); // Check if restored from cache if (event.persisted) { console.log("Page restored from bfcache"); this.onPageRestore(); } } handlePageHide(event) { console.log("Page hidden"); this.saveState(); } handleOnline() { console.log("Connection restored"); this.onOnline(); this.showNotification("You're back online"); } handleOffline() { console.log("Connection lost"); this.onOffline(); this.showNotification("You're offline. Some features may be limited."); } handleFocus() { console.log("App focused"); this.checkForUpdates(); } handleBlur() { console.log("App lost focus"); } // Lifecycle hooks onBackground() { // Pause non-critical operations this.pauseTimers(); this.trackEvent("app_backgrounded"); } onForeground() { // Resume operations this.resumeTimers(); this.refreshData(); this.trackEvent("app_foregrounded"); } onPageRestore() { // Refresh stale data this.refreshData(); } onOnline() { // Sync pending changes this.syncPendingData(); } onOffline() { // Switch to offline mode this.enableOfflineMode(); } saveState() { // Save current state const state = { timestamp: Date.now(), route: window.location.pathname, scrollPosition: window.scrollY }; localStorage.setItem("app_state", JSON.stringify(state)); } restoreState() { const savedState = localStorage.getItem("app_state"); if (savedState) { const state = JSON.parse(savedState); // Restore scroll position, etc. window.scrollTo(0, state.scrollPosition); } } trackEvent(eventName, data = {}) { if (window.analytics) { window.analytics.track(eventName, { ...data, isInstalled: this.isInstalled, displayMode: this.getDisplayMode() }); } } getDisplayMode() { const modes = ["fullscreen", "standalone", "minimal-ui", "browser"]; for (const mode of modes) { if (window.matchMedia(`(display-mode: ${mode})`).matches) { return mode; } } return "browser"; }}// Initializeconst pwaLifecycle = new PWALifecycleManager();// Usagedocument.getElementById("install-button").addEventListener("click", () => { pwaLifecycle.showInstallPrompt();});
Example: Update notification for PWA
// Service worker update detectionlet newWorker;navigator.serviceWorker.register("/sw.js").then(registration => { // Check for updates periodically setInterval(() => { registration.update(); }, 60 * 60 * 1000); // Check every hour // Listen for updates registration.addEventListener("updatefound", () => { newWorker = registration.installing; newWorker.addEventListener("statechange", () => { if (newWorker.state === "installed" && navigator.serviceWorker.controller) { // New version available showUpdateNotification(); } }); });});// Show update notificationfunction showUpdateNotification() { const notification = document.createElement("div"); notification.className = "update-notification"; notification.innerHTML = ` <p>A new version is available!</p> <button onclick="updateApp()">Update Now</button> <button onclick="dismissUpdate()">Later</button> `; document.body.appendChild(notification);}// Apply updatefunction updateApp() { if (newWorker) { newWorker.postMessage({ type: "SKIP_WAITING" }); } // Reload page when new worker takes control navigator.serviceWorker.addEventListener("controllerchange", () => { window.location.reload(); });}// In service worker (sw.js)self.addEventListener("message", (event) => { if (event.data.type === "SKIP_WAITING") { self.skipWaiting(); }});
Note: PWA lifecycle includes install, background, foreground, and network events. Use visibilitychange to detect background/foreground. Handle online/offline events for connectivity. Save state on pagehide, restore on pageshow. Check for updates on focus.
Warning: iOS Safari has limited PWA lifecycle events. beforeinstallprompt not available on iOS. Use visibilitychange instead of pagehide/pageshow on some browsers. Test lifecycle thoroughly on target platforms.
Progressive Web App APIs Best Practices
Web App Manifest: Include all required fields (name, icons, start_url, display)
Provide multiple icon sizes (72px to 512px) and maskable icons for Android
Use HTTPS - required for PWA features and service workers
Implement custom install flow with beforeinstallprompt for better UX
Don't show install prompt immediately - wait for user engagement
Note: CSS.supports() enables runtime CSS feature detection. Returns boolean indicating browser support. Use two-argument form for property-value pairs or single argument for condition strings. Combine with @supports in CSS for progressive enhancement.
Note:Avoid user agent sniffing - use feature detection instead. User agent strings are unreliable and easily spoofed. Prefer navigator.userAgentData (Client Hints) for modern browsers. Always detect features, not browsers.
Warning: User agent sniffing is an anti-pattern. UA strings can be spoofed, change frequently, and don't reflect actual capabilities. Use feature detection with CSS.supports(), checking for API existence, or try/catch. Only use UA as last resort for critical bugs in specific browsers.
18.3 MediaCapabilities API for Media Format Support
Method
Description
navigator.mediaCapabilities.decodingInfo(config)
Check if browser can decode media configuration efficiently.
navigator.mediaCapabilities.encodingInfo(config)
Check if browser can encode media configuration efficiently.
Configuration Properties
Description
type
"file" or "media-source" for decoding; "record" or "transmission" for encoding.
video
Video configuration: contentType, width, height, bitrate, framerate.
Note: MediaCapabilities API provides detailed media format support info. Returns supported, smooth, and powerEfficient booleans. Use to select optimal video/audio format based on device capabilities. More accurate than canPlayType().
18.4 Feature Policy and Permissions Policy APIs
API
Description
document.featurePolicy
Feature Policy API (deprecated, use Permissions Policy).
document.featurePolicy.allowsFeature(feature)
Check if feature is allowed in current context.
Permissions-Policy HTTP header
Control which features can be used (modern approach).
allow attribute on iframe
Grant specific permissions to embedded content.
Example: Check feature policy
// Check if Feature Policy API is availableif (document.featurePolicy) { console.log("Feature Policy supported"); // Check specific features const features = [ "camera", "microphone", "geolocation", "payment", "autoplay", "fullscreen", "picture-in-picture", "accelerometer", "gyroscope", "magnetometer" ]; features.forEach(feature => { const allowed = document.featurePolicy.allowsFeature(feature); console.log(`${feature}:`, allowed ? "allowed" : "blocked"); }); // Check with origin const allowedForOrigin = document.featurePolicy.allowsFeature( "geolocation", "https://example.com" ); console.log("Geolocation for example.com:", allowedForOrigin); // Get all allowed features const allowedFeatures = document.featurePolicy.allowedFeatures(); console.log("Allowed features:", allowedFeatures); // Get features list const allFeatures = document.featurePolicy.features(); console.log("All features:", allFeatures);} else { console.log("Feature Policy not supported");}// Modern approach: Check permissions before usingasync function requestCameraWithPolicyCheck() { // First check if allowed by policy if (document.featurePolicy && !document.featurePolicy.allowsFeature("camera")) { console.error("Camera blocked by feature policy"); return null; } // Then request permission try { const stream = await navigator.mediaDevices.getUserMedia({ video: true }); return stream; } catch (error) { console.error("Camera access denied:", error); return null; }}
Example: Set Permissions Policy in HTTP headers and HTML
<!-- Grant specific permissions to iframe --><iframe src="https://example.com/embed" allow="camera; microphone; fullscreen; payment"></iframe><!-- Allow all permissions (not recommended) --><iframe src="https://example.com/embed" allow="camera *; microphone *; geolocation *"></iframe><!-- Explicitly block features --><iframe src="https://example.com/embed" allow="fullscreen" sandbox="allow-scripts allow-same-origin"></iframe><!-- Combined with sandbox --><iframe src="https://example.com/payment" allow="payment" sandbox="allow-scripts allow-forms allow-same-origin"></iframe>
Note: Permissions Policy (formerly Feature Policy) controls access to powerful features. Set via HTTP header or iframe allow attribute. Use document.featurePolicy.allowsFeature() to check before requesting permissions. Helps prevent unauthorized feature access.
Warning: Feature Policy API is deprecated - use Permissions Policy instead. Check browser support for specific policies. Some features like geolocation require both policy allowance AND user permission. Always handle graceful degradation.
18.5 Navigator Properties and Capability Detection
Property
Description
navigator.onLine
Boolean indicating network connectivity.
navigator.language
Preferred language (e.g., "en-US").
navigator.languages
Array of preferred languages.
navigator.hardwareConcurrency
Number of logical processor cores.
navigator.deviceMemory
Approximate device RAM in GB.
navigator.connection
Network Information API object.
navigator.maxTouchPoints
Maximum number of simultaneous touch points.
navigator.cookieEnabled
Boolean indicating if cookies are enabled.
navigator.pdfViewerEnabled
Boolean indicating if built-in PDF viewer available.
// Comprehensive feature detectionconst Features = { // Storage APIs localStorage: (() => { try { const test = "__test__"; localStorage.setItem(test, test); localStorage.removeItem(test); return true; } catch (e) { return false; } })(), indexedDB: "indexedDB" in window, // Worker APIs webWorker: "Worker" in window, serviceWorker: "serviceWorker" in navigator, sharedWorker: "SharedWorker" in window, // Modern APIs fetch: "fetch" in window, promise: "Promise" in window, intersectionObserver: "IntersectionObserver" in window, resizeObserver: "ResizeObserver" in window, mutationObserver: "MutationObserver" in window, // Media APIs webRTC: "RTCPeerConnection" in window, mediaRecorder: "MediaRecorder" in window, webAudio: "AudioContext" in window || "webkitAudioContext" in window, // Graphics canvas: (() => { const canvas = document.createElement("canvas"); return !!(canvas.getContext && canvas.getContext("2d")); })(), webGL: (() => { const canvas = document.createElement("canvas"); return !!( canvas.getContext("webgl") || canvas.getContext("experimental-webgl") ); })(), // Advanced features webAssembly: typeof WebAssembly === "object", webGL2: (() => { const canvas = document.createElement("canvas"); return !!canvas.getContext("webgl2"); })(), // Input touchEvents: "ontouchstart" in window, pointerEvents: "PointerEvent" in window, // Performance performanceObserver: "PerformanceObserver" in window, // Sensors deviceOrientation: "DeviceOrientationEvent" in window, deviceMotion: "DeviceMotionEvent" in window, // Permissions permissions: "permissions" in navigator, // Payment paymentRequest: "PaymentRequest" in window, // Credentials credentials: "credentials" in navigator, webAuthn: "credentials" in navigator && "create" in navigator.credentials, // Clipboard clipboardAPI: "clipboard" in navigator, // Share webShare: "share" in navigator};console.log("Feature support:", Features);// Add feature classes to HTMLconst html = document.documentElement;Object.keys(Features).forEach(feature => { html.classList.add(Features[feature] ? feature : `no-${feature}`);});// Export for use in appexport default Features;
Note: Navigator properties provide device and browser capability info. Use hardwareConcurrency and deviceMemory for performance optimization. Check connection for adaptive loading. Feature detection more reliable than UA sniffing.
18.6 Modernizr Integration Patterns and Polyfills
Concept
Description
Modernizr
Feature detection library that adds CSS classes and JavaScript object.
Polyfill
Code that implements missing browser features.
Progressive Enhancement
Build base experience, then add features for capable browsers.
Graceful Degradation
Build full experience, provide fallbacks for older browsers.
// Polyfill loader with feature detectionasync function loadPolyfills() { const polyfills = []; // Check and load required polyfills if (!window.Promise) { polyfills.push(import("promise-polyfill")); } if (!window.fetch) { polyfills.push(import("whatwg-fetch")); } if (!window.IntersectionObserver) { polyfills.push(import("intersection-observer")); } if (!window.ResizeObserver) { polyfills.push(import("resize-observer-polyfill")); } if (!("remove" in Element.prototype)) { Element.prototype.remove = function() { if (this.parentNode) { this.parentNode.removeChild(this); } }; } if (!("prepend" in Element.prototype)) { Element.prototype.prepend = function(...nodes) { const fragment = document.createDocumentFragment(); nodes.forEach(node => { fragment.appendChild( node instanceof Node ? node : document.createTextNode(String(node)) ); }); this.insertBefore(fragment, this.firstChild); }; } // Wait for all polyfills to load await Promise.all(polyfills); console.log(`Loaded ${polyfills.length} polyfills`);}// Initialize app after polyfillsloadPolyfills().then(() => { console.log("Polyfills loaded, initializing app"); initializeApp();});// Alternative: Polyfill.io service// <script src="https://polyfill.io/v3/polyfill.min.js?features=default,fetch,IntersectionObserver"></script>// Progressive enhancement examplefunction enhanceWithModernFeatures() { // Base functionality works everywhere const items = document.querySelectorAll(".item"); // Enhance with Intersection Observer if available if ("IntersectionObserver" in window) { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { entry.target.classList.add("visible"); observer.unobserve(entry.target); } }); }); items.forEach(item => observer.observe(item)); } else { // Fallback: show all items immediately items.forEach(item => item.classList.add("visible")); }}
Note: Modernizr adds feature detection classes to HTML element. Use for progressive enhancement in CSS and JavaScript. Load polyfills only when needed. Consider using dynamic imports or Polyfill.io service for automatic detection.
Warning: Don't over-polyfill - adds bloat and slows page load. Only polyfill features you actually use. Test polyfills thoroughly. Some features can't be polyfilled (e.g., CSS Grid in IE). Consider if supporting old browsers is worth the cost.
Browser Feature Detection Best Practices
Always use feature detection, never browser detection (user agent sniffing)
CSS.supports() is the best way to detect CSS feature support
Check for API existence before using: if ("fetch" in window)
Use MediaCapabilities API for optimal media format selection
Combine multiple detection methods for robust feature checking
Leverage navigator properties (deviceMemory, hardwareConcurrency) for adaptive experiences
Use Permissions Policy to control feature access in iframes
Load polyfills conditionally - only when features are missing
Prefer progressive enhancement over graceful degradation
Add feature classes to HTML element for CSS targeting
Test feature detection code in multiple browsers
Consider build-time feature detection with Modernizr custom builds
Monitor connection quality and adapt content loading accordingly
19. Experimental and Emerging APIs
19.1 Web GPU API for Graphics Computing
WebGPU Concept
Description
GPU Adapter
Represents physical GPU device. Access via navigator.gpu.requestAdapter().
GPU Device
Logical connection to GPU for command submission. Request from adapter.
Shader Module
Compiled WGSL (WebGPU Shading Language) code for GPU execution.
Pipeline
Complete GPU program including shaders, state, and resource bindings.
Command Encoder
Records GPU commands to submit to queue.
Buffer
GPU memory for vertex data, uniforms, or compute results.
Texture
Multi-dimensional data (images) for rendering or compute.
Note: WebGPU is next-gen GPU API for web, replacing WebGL. Provides compute shaders, better performance, modern GPU features. Uses WGSL shader language. Experimental - currently in Chrome, Edge. Check navigator.gpu before use.
Warning: WebGPU is experimental and not widely supported yet. API may change. Always provide WebGL fallback. Requires HTTPS. Shaders use WGSL, not GLSL. GPU device can be lost - handle device.lost promise.
19.2 Web Assembly (WASM) Integration APIs
WebAssembly API
Description
WebAssembly.compile()
Compile WASM bytes to module asynchronously.
WebAssembly.instantiate()
Compile and instantiate WASM module in one step.
WebAssembly.Module
Compiled WASM module (stateless, can be cached).
WebAssembly.Instance
Instantiated module with memory and exported functions.
WebAssembly.Memory
Resizable ArrayBuffer for WASM linear memory.
WebAssembly.Table
Resizable typed array of references (functions, objects).
WebAssembly.Global
Global variable accessible to WASM and JavaScript.
// Create shared memory for multi-threaded WASMconst sharedMemory = new WebAssembly.Memory({ initial: 1, maximum: 10, shared: true // Enable sharing between workers});// Main thread: Load WASM with shared memoryasync function loadThreadedWasm() { const response = await fetch("threaded.wasm"); const { instance } = await WebAssembly.instantiateStreaming(response, { env: { memory: sharedMemory }, wasi_snapshot_preview1: { // WASI imports if needed } }); return instance;}// Worker thread code (worker.js)/*self.onmessage = async (event) => { const { memory, wasmUrl } = event.data; const response = await fetch(wasmUrl); const { instance } = await WebAssembly.instantiateStreaming(response, { env: { memory } }); // Run compute-intensive work const result = instance.exports.processData(); self.postMessage({ result });};*/// Use from main threadasync function runParallelWasm() { const wasmInstance = await loadThreadedWasm(); // Spawn workers const workers = []; const numWorkers = navigator.hardwareConcurrency || 4; for (let i = 0; i < numWorkers; i++) { const worker = new Worker("worker.js"); worker.postMessage({ memory: sharedMemory, wasmUrl: "threaded.wasm" }); workers.push(worker); } // Coordinate work via shared memory and Atomics const sharedArray = new Int32Array(sharedMemory.buffer); // Signal workers to start Atomics.store(sharedArray, 0, 1); Atomics.notify(sharedArray, 0); console.log("Workers started");}// WASM Binary Interface exampleclass WasmBinaryInterface { constructor(instance) { this.instance = instance; this.memory = new DataView(instance.exports.memory.buffer); } // Read string from WASM memory readString(ptr, length) { const bytes = new Uint8Array(this.instance.exports.memory.buffer, ptr, length); return new TextDecoder().decode(bytes); } // Write string to WASM memory writeString(str, ptr) { const bytes = new TextEncoder().encode(str); const memory = new Uint8Array(this.instance.exports.memory.buffer); memory.set(bytes, ptr); return bytes.length; } // Read array from memory readArray(ptr, length, type = Float32Array) { return new type(this.instance.exports.memory.buffer, ptr, length); }}
Note: WebAssembly enables near-native performance for compute-intensive tasks. Use for: video/audio processing, games, simulations, cryptography. Import/export functions between JS and WASM. Use instantiateStreaming() for faster loading. Supports multithreading with SharedArrayBuffer.
19.3 Web Neural Network API (WebNN)
WebNN Concept
Description
ML Context
Hardware-accelerated context for neural network operations.
Graph Builder
Builds ML computation graph with layers and operations.
Operand
Tensor (multi-dimensional array) in the computation graph.
Operation
Neural network operation: conv2d, matmul, relu, softmax, etc.
Graph
Compiled computation graph ready for execution.
Backend
Hardware backend: CPU, GPU, or dedicated ML accelerator.
Note: WebNN provides hardware-accelerated machine learning inference. Uses GPU, NPU, or dedicated ML accelerators. Supports common operations: conv2d, matmul, relu, softmax. Can load ONNX models. Experimental - limited browser support.
Warning: WebNN is highly experimental. Currently only in some Chromium builds behind flags. API subject to change. For production, use TensorFlow.js or ONNX Runtime Web with WASM backend. Always check "ml" in navigator and provide fallback.
19.4 Web Transport API for Low-latency Communication
WebTransport Feature
Description
WebTransport Connection
Low-latency, bidirectional communication over HTTP/3 (QUIC protocol).
Datagrams
Unreliable, unordered messages (like UDP) for real-time data.
Streams
Reliable, ordered streams (like TCP) with multiplexing.
Unidirectional Streams
One-way data flow from client or server.
Bidirectional Streams
Two-way data flow, independent read/write.
Connection Migration
Maintains connection across network changes (WiFi to cellular).
Example: WebTransport connection and streams
// Check WebTransport supportif (!("WebTransport" in window)) { console.error("WebTransport not supported");} else { console.log("WebTransport supported"); connectWebTransport();}async function connectWebTransport() { try { // Connect to server (requires HTTPS and HTTP/3) const url = "https://example.com:4433/webtransport"; const transport = new WebTransport(url); // Wait for connection await transport.ready; console.log("WebTransport connected"); // Handle incoming bidirectional streams handleIncomingStreams(transport); // Handle incoming unidirectional streams handleIncomingUnidirectionalStreams(transport); // Send data via bidirectional stream await sendViaBidirectionalStream(transport); // Send datagrams sendDatagrams(transport); // Handle connection close transport.closed.then(() => { console.log("Connection closed gracefully"); }).catch((error) => { console.error("Connection closed with error:", error); }); } catch (error) { console.error("WebTransport connection failed:", error); }}// Bidirectional streams (reliable, ordered)async function sendViaBidirectionalStream(transport) { // Create outgoing bidirectional stream const stream = await transport.createBidirectionalStream(); // Get writer and reader const writer = stream.writable.getWriter(); const reader = stream.readable.getReader(); // Send data const encoder = new TextEncoder(); await writer.write(encoder.encode("Hello from client!")); await writer.close(); console.log("Sent via bidirectional stream"); // Read response const { value, done } = await reader.read(); if (!done) { const decoder = new TextDecoder(); console.log("Received:", decoder.decode(value)); } reader.releaseLock();}// Handle incoming bidirectional streamsasync function handleIncomingStreams(transport) { const reader = transport.incomingBidirectionalStreams.getReader(); while (true) { const { value: stream, done } = await reader.read(); if (done) break; console.log("Incoming bidirectional stream"); // Handle stream in background handleStream(stream); }}async function handleStream(stream) { const reader = stream.readable.getReader(); const writer = stream.writable.getWriter(); try { const { value, done } = await reader.read(); if (!done) { const decoder = new TextDecoder(); const message = decoder.decode(value); console.log("Stream received:", message); // Send response const encoder = new TextEncoder(); await writer.write(encoder.encode(`Echo: ${message}`)); } } finally { reader.releaseLock(); await writer.close(); }}// Unidirectional streamsasync function handleIncomingUnidirectionalStreams(transport) { const reader = transport.incomingUnidirectionalStreams.getReader(); while (true) { const { value: stream, done } = await reader.read(); if (done) break; console.log("Incoming unidirectional stream"); const streamReader = stream.getReader(); const { value, done: streamDone } = await streamReader.read(); if (!streamDone) { const decoder = new TextDecoder(); console.log("Unidirectional data:", decoder.decode(value)); } }}
Example: WebTransport datagrams for real-time data
// Datagrams (unreliable, unordered - best for real-time)async function sendDatagrams(transport) { const writer = transport.datagrams.writable.getWriter(); const encoder = new TextEncoder(); // Send game state updates setInterval(async () => { const gameState = { position: { x: Math.random(), y: Math.random() }, timestamp: Date.now() }; const data = encoder.encode(JSON.stringify(gameState)); try { await writer.write(data); } catch (error) { console.error("Failed to send datagram:", error); } }, 16); // ~60 FPS}// Receive datagramsasync function receiveDatagrams(transport) { const reader = transport.datagrams.readable.getReader(); const decoder = new TextDecoder(); while (true) { try { const { value, done } = await reader.read(); if (done) break; const message = decoder.decode(value); const data = JSON.parse(message); // Update game state updateGameState(data); } catch (error) { console.error("Datagram read error:", error); } }}// Real-time multiplayer game exampleclass WebTransportGameClient { constructor(serverUrl) { this.serverUrl = serverUrl; this.transport = null; this.datagramWriter = null; } async connect() { this.transport = new WebTransport(this.serverUrl); await this.transport.ready; console.log("Game client connected"); this.datagramWriter = this.transport.datagrams.writable.getWriter(); // Start receiving updates this.receiveUpdates(); } async sendPlayerAction(action) { if (!this.datagramWriter) return; const data = new TextEncoder().encode(JSON.stringify({ type: "action", action: action, timestamp: performance.now() })); try { await this.datagramWriter.write(data); } catch (error) { console.error("Failed to send action:", error); } } async receiveUpdates() { const reader = this.transport.datagrams.readable.getReader(); const decoder = new TextDecoder(); while (true) { try { const { value, done } = await reader.read(); if (done) break; const update = JSON.parse(decoder.decode(value)); this.handleGameUpdate(update); } catch (error) { console.error("Failed to receive update:", error); break; } } } handleGameUpdate(update) { // Process game state update console.log("Game update:", update); } async disconnect() { if (this.transport) { await this.transport.close(); } }}
Note: WebTransport offers lower latency than WebSocket using HTTP/3 (QUIC). Provides both reliable streams and unreliable datagrams. Ideal for: gaming, video conferencing, live streaming. Supports connection migration (network switching). Experimental - Chrome, Edge support.
Warning: WebTransport requires HTTP/3 server support and HTTPS. Experimental - limited browser support. Not available on iOS Safari. Server setup more complex than WebSocket. Consider WebSocket for broader compatibility.
19.5 WebCodecs API for Audio/Video Encoding
WebCodecs Component
Description
VideoEncoder
Encode video frames to compressed format (H.264, VP9, AV1).
VideoDecoder
Decode compressed video to raw frames.
AudioEncoder
Encode audio data to compressed format (Opus, AAC).
AudioDecoder
Decode compressed audio to PCM samples.
VideoFrame
Represents a single video frame with pixel data.
EncodedVideoChunk
Encoded video data (can be keyframe or delta frame).
Note: WebCodecs provides low-level access to media codecs. Encode/decode video and audio programmatically. Useful for: video editing, conferencing, streaming, transcoding. More control than MediaRecorder API. Supports H.264, VP8, VP9, AV1 for video; Opus, AAC for audio.
Warning: WebCodecs is low-level - you handle frame-by-frame encoding/decoding. Must manage memory by calling frame.close(). Not all codecs supported in all browsers. Check codec support with VideoEncoder.isConfigSupported(). Chrome, Edge support - not in Firefox/Safari yet.
19.6 Origin Trial APIs and Feature Flags
Concept
Description
Origin Trial
Test experimental features in production with token from Chrome.
Feature Flag
Enable experimental features locally via browser flags.
Origin Trial Token
Signed token that enables feature for specific origin and duration.
chrome://flags
Browser page to enable experimental features for testing.
Meta Tag Registration
Activate origin trial via <meta> tag in HTML.
HTTP Header Registration
Activate origin trial via Origin-Trial HTTP response header.
Example: Register for Origin Trial
<!-- Method 1: Meta tag --><meta http-equiv="origin-trial" content="YOUR_ORIGIN_TRIAL_TOKEN_HERE"><!-- Example with actual token format --><meta http-equiv="origin-trial" content="A1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6Q7r8S9t0U1v2W3x4Y5z6A7b8C9d0E1f2G3h4I5j6K7l8M9n0O1p2Q3r4S5t6U7v8W9x0Y1z2A3b4C5d6E7f8G9h0I1j2K3l4M5n6O7p8Q9r0S1t2U3v4W5x6Y7z8A9b0C1d2E3f4=="><script> // Check if experimental feature is available if ("experimentalFeature" in window) { console.log("Experimental feature enabled via origin trial"); useExperimentalFeature(); } else { console.log("Experimental feature not available"); useFallback(); }</script>
Example: HTTP header registration and feature detection
// Check for experimental featuresconst experimentalFeatures = { // Web GPU webGPU: "gpu" in navigator, // WebNN webNN: "ml" in navigator, // Web Transport webTransport: "WebTransport" in window, // WebCodecs webCodecs: "VideoEncoder" in window, // File System Access fileSystemAccess: "showOpenFilePicker" in window, // Web Bluetooth webBluetooth: "bluetooth" in navigator, // Web USB webUSB: "usb" in navigator, // Web Serial webSerial: "serial" in navigator, // Web HID webHID: "hid" in navigator, // Compute Pressure computePressure: "ComputePressureObserver" in window, // Eyedropper API eyeDropper: "EyeDropper" in window, // Window Controls Overlay windowControlsOverlay: "windowControlsOverlay" in navigator};console.log("Experimental features:", experimentalFeatures);// Progressive enhancement with feature detectionfunction useExperimentalFeatures() { // WebGPU if available, else WebGL if (experimentalFeatures.webGPU) { initWebGPU(); } else { initWebGL(); } // Web Transport if available, else WebSocket if (experimentalFeatures.webTransport) { connectWebTransport(); } else { connectWebSocket(); }}// Origin Trial status checkasync function checkOriginTrialStatus() { // Some APIs expose trial status if (document.featurePolicy) { const features = document.featurePolicy.features(); console.log("Available features:", features); } // Check specific trial features try { // Attempt to use feature const feature = await tryExperimentalFeature(); console.log("Origin trial active for feature"); return true; } catch (error) { console.log("Origin trial not active:", error.message); return false; }}// Get Origin Trial token programmaticallyfunction getOriginTrialToken() { // For your actual implementation, get token from: // https://developer.chrome.com/origintrials/ // Sign up for trial // 1. Go to Chrome Origin Trials // 2. Select feature to trial // 3. Enter your origin (https://example.com) // 4. Receive token // 5. Add to meta tag or HTTP header return "YOUR_TOKEN_HERE";}// Enable feature flags for local developmentconsole.log(`To enable experimental features locally:1. Open chrome://flags2. Search for feature name3. Enable and restart browserCommon flags:- #enable-experimental-web-platform-features- #enable-webgpu-developer-features- #enable-experimental-webassembly-features`);
Note: Origin Trials let you test experimental features in production. Register at Chrome Origin Trials site, get token for your domain. Token enables feature for all users for trial duration (usually 6 months). Use for gathering feedback before feature ships.
Warning: Origin Trial features may change or be removed. Don't rely on them for critical functionality. Always provide fallback. Trial tokens expire - monitor trial status. Features may become available without trial when they ship. Test thoroughly - experimental APIs can have bugs.
Experimental and Emerging APIs Best Practices
Always check feature availability before use: if ("feature" in window)
Provide fallbacks for unsupported browsers - progressive enhancement
WebGPU: Use for compute-heavy graphics, provide WebGL fallback
WebAssembly: Great for performance-critical code, use instantiateStreaming()
WebNN: Hardware ML acceleration, fallback to TensorFlow.js WASM backend
WebTransport: Lower latency than WebSocket, fallback to WebSocket
WebCodecs: Low-level media control, requires manual memory management
Register for Origin Trials to test features in production safely
Monitor experimental API specifications - they can change
Use feature flags locally (chrome://flags) for development
Document which experimental features your app uses
Have migration plan when experimental features become stable
Test on multiple browsers - experimental features may not be cross-browser
20. API Integration and Development Patterns
20.1 API Polyfills and Feature Detection Patterns
Pattern
Description
Feature Detection First
Always check if API exists before using: if ("fetch" in window).
Conditional Polyfill Loading
Load polyfills only when needed to reduce bundle size.
Progressive Enhancement
Build base functionality, enhance for capable browsers.
Polyfill Service
Use CDN service (polyfill.io) for automatic polyfill delivery.
Core-js Integration
Use core-js for comprehensive ES6+ and Web API polyfills.
Babel Transformation
Transform modern syntax to compatible code at build time.
// Dynamic polyfill loaderasync function loadPolyfills() { const polyfillsNeeded = []; // Check and queue polyfills if (!window.fetch) { polyfillsNeeded.push( import("whatwg-fetch") ); } if (!window.Promise) { polyfillsNeeded.push( import("promise-polyfill") ); } if (!window.IntersectionObserver) { polyfillsNeeded.push( import("intersection-observer") ); } if (!window.ResizeObserver) { polyfillsNeeded.push( import("resize-observer-polyfill") ); } if (!Element.prototype.closest) { Element.prototype.closest = function(selector) { let el = this; while (el) { if (el.matches(selector)) return el; el = el.parentElement; } return null; }; } if (!Array.prototype.includes) { Array.prototype.includes = function(searchElement, fromIndex) { return this.indexOf(searchElement, fromIndex) !== -1; }; } // Load all polyfills in parallel if (polyfillsNeeded.length > 0) { console.log(`Loading ${polyfillsNeeded.length} polyfills...`); await Promise.all(polyfillsNeeded); console.log("Polyfills loaded"); }}// Initialize app after polyfillsloadPolyfills().then(() => { console.log("Starting application"); initApp();});// Alternative: Use Polyfill.io CDN/*<script src="https://polyfill.io/v3/polyfill.min.js?features=fetch,Promise,IntersectionObserver"></script>*/// Alternative: Conditional script loadingfunction loadPolyfillScript(url) { return new Promise((resolve, reject) => { const script = document.createElement("script"); script.src = url; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); });}async function loadLegacyPolyfills() { if (!window.Promise) { await loadPolyfillScript("https://cdn.jsdelivr.net/npm/promise-polyfill@8/dist/polyfill.min.js"); } if (!window.fetch) { await loadPolyfillScript("https://cdn.jsdelivr.net/npm/whatwg-fetch@3/dist/fetch.umd.js"); }}
Note: Always use feature detection, never browser sniffing. Load polyfills conditionally to reduce bundle size. Use dynamic imports for code splitting. Consider using Polyfill.io CDN for automatic polyfill delivery based on user agent.
20.2 Error Handling and Graceful Degradation
Strategy
Description
Try-Catch Blocks
Wrap API calls in try-catch for synchronous and async/await code.
Promise Error Handling
Use .catch() or try-catch with async/await for promise rejections.
Global Error Handler
Use window.onerror and window.onunhandledrejection for uncaught errors.
Fallback Strategies
Provide alternative implementations when APIs unavailable.
User Feedback
Show meaningful error messages to users, log details for developers.
Retry Logic
Implement exponential backoff for transient failures.
Note: Always handle errors at multiple levels: API level, function level, and global level. Provide fallbacks for missing APIs. Use retry logic for transient failures. Show user-friendly messages while logging detailed errors for debugging.
20.3 Performance Optimization for API Usage
Technique
Description
Debouncing
Delay API calls until user stops action (e.g., typing).
Throttling
Limit API call frequency (e.g., max once per 100ms).
Request Caching
Cache API responses to avoid redundant requests.
Request Deduplication
Prevent duplicate simultaneous requests for same resource.
// Request batchingclass RequestBatcher { constructor(batchInterval = 50) { this.queue = []; this.timer = null; this.batchInterval = batchInterval; } add(request) { return new Promise((resolve, reject) => { this.queue.push({ request, resolve, reject }); if (!this.timer) { this.timer = setTimeout(() => { this.flush(); }, this.batchInterval); } }); } async flush() { if (this.queue.length === 0) return; const batch = this.queue.splice(0); this.timer = null; try { // Send batched request const requests = batch.map(b => b.request); const response = await fetch("/api/batch", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ requests }) }); const results = await response.json(); // Resolve individual promises batch.forEach((item, index) => { item.resolve(results[index]); }); } catch (error) { // Reject all promises batch.forEach(item => { item.reject(error); }); } }}// Usageconst batcher = new RequestBatcher();async function fetchUser(id) { return batcher.add({ type: "user", id });}// These will be batched into single requestPromise.all([ fetchUser(1), fetchUser(2), fetchUser(3)]).then(users => { console.log("Users:", users);});// Web Worker for heavy processingconst workerCode = ` self.onmessage = function(event) { const { data, operation } = event.data; let result; switch (operation) { case "processImage": result = processImage(data); break; case "parseData": result = parseData(data); break; } self.postMessage({ result }); }; function processImage(imageData) { // Heavy image processing return imageData; } function parseData(data) { // Heavy data parsing return data; }`;// Create workerconst blob = new Blob([workerCode], { type: "application/javascript" });const workerUrl = URL.createObjectURL(blob);const worker = new Worker(workerUrl);// Use workerworker.postMessage({ operation: "processImage", data: imageData});worker.onmessage = (event) => { console.log("Worker result:", event.data.result);};
Note: Performance optimization is critical for good UX. Debounce user input, throttle scroll/resize handlers. Cache API responses with appropriate TTL. Deduplicate simultaneous requests. Use Web Workers for CPU-intensive tasks to keep main thread responsive.
20.4 Cross-Browser Compatibility Strategies
Strategy
Description
Feature Detection
Check API availability before use, provide fallbacks.
Vendor Prefixes
Try standard API first, then vendor-prefixed versions.
Autoprefixer
Use build tools to add vendor prefixes automatically.
Browserslist
Define target browsers for transpilation and polyfills.
Babel
Transpile modern JavaScript to compatible versions.
Note: Prefer feature detection over browser detection. Use vendor prefixes for CSS and JavaScript APIs when needed. Leverage build tools (Babel, Autoprefixer) for automatic compatibility. Define target browsers with Browserslist. Test on actual devices, not just browser DevTools.
20.5 API Mocking and Testing Patterns
Pattern
Description
Mock Service Worker
Intercept network requests at Service Worker level for testing.
// Test helpers for API testingclass APITestHelpers { constructor(baseURL) { this.baseURL = baseURL; } async waitForCondition(conditionFn, timeout = 5000) { const startTime = Date.now(); while (Date.now() - startTime < timeout) { if (await conditionFn()) { return true; } await this.sleep(100); } throw new Error("Timeout waiting for condition"); } sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async setupTestData() { // Create test data in database await fetch(`${this.baseURL}/test/setup`, { method: "POST" }); } async cleanupTestData() { // Clean up test data await fetch(`${this.baseURL}/test/cleanup`, { method: "POST" }); }}// Integration test exampledescribe("User API Integration", () => { const helpers = new APITestHelpers("http://localhost:3000"); beforeEach(async () => { await helpers.setupTestData(); }); afterEach(async () => { await helpers.cleanupTestData(); }); test("creates and retrieves user", async () => { // Create user const createResponse = await fetch("http://localhost:3000/api/users", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: "Test User", email: "test@example.com" }) }); expect(createResponse.ok).toBe(true); const createdUser = await createResponse.json(); expect(createdUser.id).toBeDefined(); // Retrieve user const getResponse = await fetch( `http://localhost:3000/api/users/${createdUser.id}` ); expect(getResponse.ok).toBe(true); const retrievedUser = await getResponse.json(); expect(retrievedUser.name).toBe("Test User"); }); test("handles network errors gracefully", async () => { // Test with invalid URL const response = await fetch("http://localhost:3000/api/invalid"); expect(response.ok).toBe(false); expect(response.status).toBe(404); });});
Note: Mock APIs for unit tests, use real APIs for integration tests. Mock Service Worker provides realistic network mocking. Use dependency injection to make code testable. Mock browser APIs (localStorage, IntersectionObserver) in test environment.
20.6 Progressive Enhancement with Modern APIs
Principle
Description
Core Functionality First
Ensure basic features work without modern APIs.
Enhancement Layers
Add features progressively based on capability detection.
Resilient Foundation
Build on HTML/CSS, enhance with JavaScript.
Feature Queries
Use CSS @supports and JavaScript feature detection.
Adaptive Loading
Adjust features based on device capabilities and network.
Graceful Fallbacks
Provide alternative experiences when APIs unavailable.
Example: Progressive enhancement framework
// Progressive enhancement managerclass ProgressiveEnhancement { constructor() { this.features = this.detectFeatures(); this.applyEnhancements(); } detectFeatures() { return { // Core APIs fetch: "fetch" in window, promises: "Promise" in window, // Storage localStorage: this.testLocalStorage(), indexedDB: "indexedDB" in window, // Observers intersectionObserver: "IntersectionObserver" in window, resizeObserver: "ResizeObserver" in window, // Modern features serviceWorker: "serviceWorker" in navigator, webGL: this.testWebGL(), webAssembly: typeof WebAssembly === "object", // Device capabilities touch: "ontouchstart" in window, deviceMemory: navigator.deviceMemory, connection: navigator.connection?.effectiveType }; } testLocalStorage() { try { const test = "__test__"; localStorage.setItem(test, test); localStorage.removeItem(test); return true; } catch (e) { return false; } } testWebGL() { const canvas = document.createElement("canvas"); return !!( canvas.getContext("webgl") || canvas.getContext("experimental-webgl") ); } applyEnhancements() { const html = document.documentElement; // Add feature classes Object.keys(this.features).forEach(feature => { if (this.features[feature]) { html.classList.add(`has-${feature}`); } else { html.classList.add(`no-${feature}`); } }); // Apply enhancements based on features if (this.features.intersectionObserver) { this.enableLazyLoading(); } if (this.features.serviceWorker) { this.registerServiceWorker(); } if (this.features.webGL) { this.enable3DGraphics(); } // Adaptive loading based on device if (this.features.connection === "slow-2g" || this.features.connection === "2g") { this.enableDataSaver(); } if (this.features.deviceMemory && this.features.deviceMemory < 4) { this.reduceFeaturesForLowMemory(); } } enableLazyLoading() { const images = document.querySelectorAll("img[data-src]"); const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; observer.unobserve(img); } }); }); images.forEach(img => observer.observe(img)); } async registerServiceWorker() { try { await navigator.serviceWorker.register("/sw.js"); console.log("Service Worker registered"); } catch (error) { console.error("SW registration failed:", error); } } enable3DGraphics() { console.log("3D graphics available"); // Initialize WebGL-based features } enableDataSaver() { console.log("Data saver mode enabled"); // Reduce image quality, disable autoplay, etc. } reduceFeaturesForLowMemory() { console.log("Reducing features for low memory device"); // Simplify UI, reduce animations, etc. }}// Initialize on page loaddocument.addEventListener("DOMContentLoaded", () => { new ProgressiveEnhancement();});
Example: Adaptive component loading
// Adaptive component loaderclass AdaptiveLoader { constructor() { this.capabilities = this.assessCapabilities(); } assessCapabilities() { return { tier: this.getDeviceTier(), network: this.getNetworkQuality(), features: this.getSupportedFeatures() }; } getDeviceTier() { const memory = navigator.deviceMemory || 4; const cores = navigator.hardwareConcurrency || 2; if (memory >= 8 && cores >= 8) return "high"; if (memory >= 4 && cores >= 4) return "medium"; return "low"; } getNetworkQuality() { const connection = navigator.connection; if (!connection) return "unknown"; const effectiveType = connection.effectiveType; if (effectiveType === "4g") return "fast"; if (effectiveType === "3g") return "medium"; return "slow"; } getSupportedFeatures() { return { webGL: !!document.createElement("canvas").getContext("webgl"), webAssembly: typeof WebAssembly === "object", workers: typeof Worker !== "undefined" }; } async loadComponent(componentName) { const { tier, network, features } = this.capabilities; // Load appropriate version based on capabilities if (tier === "high" && network === "fast") { // Load full-featured version return import(`./components/${componentName}/full.js`); } else if (tier === "medium" || network === "medium") { // Load standard version return import(`./components/${componentName}/standard.js`); } else { // Load lite version return import(`./components/${componentName}/lite.js`); } } async loadMedia(mediaUrl) { const { tier, network } = this.capabilities; // Select appropriate media quality let quality; if (tier === "high" && network === "fast") { quality = "high"; } else if (network === "slow" || tier === "low") { quality = "low"; } else { quality = "medium"; } return `${mediaUrl}?quality=${quality}`; }}// Usageconst loader = new AdaptiveLoader();// Load component based on device capabilitiesloader.loadComponent("VideoPlayer").then(({ default: VideoPlayer }) => { const player = new VideoPlayer(); player.init();});// Load appropriate media qualityconst videoUrl = await loader.loadMedia("/videos/intro.mp4");videoElement.src = videoUrl;
Note: Progressive enhancement ensures everyone gets working experience, with better browsers getting enhanced features. Build resilient foundation with HTML/CSS. Enhance with JavaScript based on feature detection. Adapt to device capabilities and network conditions.
Warning: Don't assume all users have modern browsers or fast connections. Test on low-end devices and slow networks. Don't break core functionality for users without cutting-edge features. Monitor real-user metrics to understand actual device/network distribution.
API Integration and Development Best Practices
Always use feature detection, never assume API availability
Load polyfills conditionally to minimize bundle size
Implement comprehensive error handling at all levels
Provide meaningful fallbacks when APIs unavailable
Debounce user input and throttle high-frequency events
Cache API responses with appropriate TTL to reduce network calls
Deduplicate simultaneous requests for same resource
Use Web Workers for CPU-intensive operations
Test across multiple browsers and devices
Mock APIs for unit tests, use real APIs for integration tests
Practice progressive enhancement - build on solid foundation
Adapt features based on device capabilities and network quality