IntersectionObserver, MutationObserver & ResizeObserver
Scroll event bhool jao, Observer lagao. Browser khud batayega — visible hua, DOM badla, size change hua.
IntersectionObserver watches elements and tells you when they enter or leave the viewport (or a root element). It replaces scroll event listeners — much more performant because it runs after layout and uses GPU layer positions.
Creating an observer: new IntersectionObserver(callback, options) where options include:
• root: The element used as the viewport for checking visibility (defaults to browser viewport / null).
• rootMargin: Expand or shrink the observation area like CSS margin. For example, rootMargin: '200px' triggers 200px before the element enters the viewport.
• threshold: 0 to 1 — the percentage of the target visible that triggers the callback. 0 = any pixel, 0.5 = 50%, 1 = fully visible.
The callback receives an array of IntersectionObserverEntry objects: isIntersecting, intersectionRatio, target, boundingClientRect, rootBounds.
Key methods: observer.observe(element) starts watching. observer.unobserve(element) stops watching one element. observer.disconnect() stops all observations.
Use cases: Lazy loading images, infinite scroll, reveal-on-scroll animations, ad impression tracking.
// Lazy loading images with IntersectionObserver
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // Load the real image
img.classList.add('loaded');
observer.unobserve(img); // Stop watching — loaded!
}
});
}, {
root: null, // Use viewport
rootMargin: '200px', // Start loading 200px BEFORE visible
threshold: 0 // Trigger when even 1px is visible
});
// Observe all lazy images
document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});
// Reveal-on-scroll animation
const revealObserver = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('revealed');
}
});
}, { threshold: 0.2 }); // Trigger when 20% visible
document.querySelectorAll('.animate-on-scroll').forEach(el => {
revealObserver.observe(el);
});
MutationObserver watches a DOM element for changes to its children, attributes, or text content. It is the DOM change detector — when anything in the subtree changes, you get notified.
Creating an observer: new MutationObserver(callback) then observer.observe(target, config).
Config options:
• childList: Watch for child additions and removals.
• attributes: Watch for attribute changes on the target.
• characterData: Watch for text content changes.
• subtree: Watch ALL descendants, not just direct children.
The callback receives an array of MutationRecord objects: type, target, addedNodes, removedNodes, attributeName, oldValue.
Important: Set attributeOldValue: true and characterDataOldValue: true if you need the old value in records.
// Watch for DOM changes
const target = document.getElementById('dynamic-content');
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
console.log('Children changed!');
console.log('Added:', mutation.addedNodes);
console.log('Removed:', mutation.removedNodes);
}
if (mutation.type === 'attributes') {
console.log('Attribute changed:', mutation.attributeName);
console.log('Old value:', mutation.oldValue);
}
if (mutation.type === 'characterData') {
console.log('Text changed!');
console.log('Old text:', mutation.oldValue);
}
});
});
observer.observe(target, {
childList: true, // Watch for added/removed children
attributes: true, // Watch for attribute changes
characterData: true, // Watch for text content changes
subtree: true, // Watch ALL descendants
attributeOldValue: true, // Include old attribute value
characterDataOldValue: true // Include old text value
});
// Stop watching
// observer.disconnect();
ResizeObserver watches an element's size — fires when the element's content rectangle changes. Different from window.resize: ResizeObserver watches INDIVIDUAL elements, not just the viewport.
Use case: Responsive components that adapt when their container resizes (not just the window). Perfect for widget-based architectures.
Callback receives an array of ResizeObserverEntry objects: target, contentRect (DOMRectReadOnly), borderBoxSize, contentBoxSize.
contentRect has: x, y, width, height, top, right, bottom, left.
Warning: ResizeObserver may cause layout thrashing if the callback resizes elements — use requestAnimationFrame to break the cycle.
// Responsive component that adapts to container size
const container = document.getElementById('widget');
const resizeObserver = new ResizeObserver((entries) => {
for (const entry of entries) {
const width = entry.contentRect.width;
// Adapt layout based on container width
if (width < 400) {
container.classList.remove('layout-medium', 'layout-large');
container.classList.add('layout-small');
} else if (width < 800) {
container.classList.remove('layout-small', 'layout-large');
container.classList.add('layout-medium');
} else {
container.classList.remove('layout-small', 'layout-medium');
container.classList.add('layout-large');
}
console.log('Container width:', width);
}
});
resizeObserver.observe(container);
// ⚠️ BE CAREFUL: Don't resize the observed element in the callback!
// This can cause an infinite loop:
// resizeObserver observes container → callback makes container wider →
// ResizeObserver fires again → callback makes container wider → INFINITE!
// ✅ Safe: Use requestAnimationFrame for resize operations
const safeObserver = new ResizeObserver((entries) => {
requestAnimationFrame(() => {
// Resize operations here — runs once per frame, not per callback
updateLayout(entries);
});
});Clipboard API lets you read from and write to the system clipboard programmatically. navigator.clipboard.writeText(text) copies text (needs HTTPS + user gesture). navigator.clipboard.readText() reads text (needs permission).
Clipboard requires a Secure Context (HTTPS or localhost) AND transient user activation (click/keypress). Missing either = NotAllowedError.
For rich data: navigator.clipboard.write([new ClipboardItem({'text/plain': blob})]).
Page Visibility API tells you whether the page is visible to the user. document.visibilityState returns 'visible' or 'hidden'. Listen with document.addEventListener('visibilitychange', handler).
Use the Visibility API to: pause video/animations when tab is hidden, stop polling, reduce CPU usage.
document.hidden is a boolean shortcut — true when the tab is not visible.
// Clipboard API — copy on button click
copyBtn.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText('Hello Hyderabad!');
console.log('Copied!');
} catch (err) {
// Fallback for older browsers or non-HTTPS
const textarea = document.createElement('textarea');
textarea.value = 'Hello Hyderabad!';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
});
// Clipboard API — paste
pasteBtn.addEventListener('click', async () => {
try {
const text = await navigator.clipboard.readText();
console.log('Pasted:', text);
} catch (err) {
console.log('Permission denied or not HTTPS');
}
});
// Page Visibility — pause when tab hidden
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
console.log('Tab visible — resume animations, polling');
startPolling();
resumeAnimations();
} else {
console.log('Tab hidden — pause to save CPU');
stopPolling();
pauseAnimations();
}
});ALWAYS disconnect observers when they are no longer needed — prevents memory leaks. In SPAs: disconnect on component unmount, re-observe on mount.
IntersectionObserver: Use unobserve() after one-time actions (like lazy loading). Use disconnect() to stop all.
MutationObserver: Use disconnect() when you are done watching. It also stops all pending callbacks.
ResizeObserver: Be careful not to resize observed elements in the callback — causes infinite loop.
Observer pattern vs Event Listener: Observers are for specific monitoring tasks (visibility, mutations, size). Event listeners are for general interaction (click, keydown). Observers are much cheaper than polling with setInterval or scroll events.
// ✅ Proper cleanup pattern
class LazyLoader {
constructor() {
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadImage(entry.target);
this.observer.unobserve(entry.target);
}
});
}, { rootMargin: '200px' });
}
init() {
document.querySelectorAll('img[data-src]').forEach(img => {
this.observer.observe(img);
});
}
destroy() {
this.observer.disconnect(); // ✅ Clean up! Prevents memory leaks!
}
}
// Visibility API cleanup
const pollInterval = setInterval(fetchData, 5000);
document.addEventListener('visibilitychange', () => {
if (document.hidden) {
clearInterval(pollInterval); // Stop polling when hidden
} else {
setInterval(fetchData, 5000); // Resume when visible
}
});
// Performance comparison:
// ❌ Scroll event listener (runs on EVERY scroll frame)
// window.addEventListener('scroll', checkVisibility); // 60fps × check = expensive
// ✅ IntersectionObserver (runs only when visibility CHANGES)
// const observer = new IntersectionObserver(callback); // Only on change = cheap
Lo kar liya — Key Points:
- ✅ IntersectionObserver watches elements for visibility changes — replaces expensive scroll event listeners for lazy loading and reveal animations
- ✅ rootMargin expands the observation area (like CSS margin) and threshold sets the percentage visible that triggers the callback
- ✅ MutationObserver watches DOM elements for child, attribute, or text changes — callbacks are batched as microtasks
- ✅ Always set attributeOldValue and characterDataOldValue to true if you need the old value in MutationRecord
- ✅ ResizeObserver watches individual element size changes — different from window.resize which only watches the viewport
- ✅ Be careful not to resize observed elements in ResizeObserver callbacks — can cause infinite loops
- ✅ Clipboard API requires HTTPS and user gesture — always provide a fallback for non-HTTPS contexts
- ✅ Page Visibility API lets you pause expensive work when the tab is hidden — use visibilitychange event
- ✅ Always disconnect observers when they're no longer needed to prevent memory leaks
Want to track your progress?
Log in to save your place and pick up where you left off.
Progress track karna chahte ho?
Login karo apni progress save karne ke liye aur jahan chhoda tha wahan se shuru karo.
Login