Suggest an editImprove this articleRefine the answer for “IntersectionObserver”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**`IntersectionObserver` is a built-in browser API that watches an element and reports when it enters or leaves the viewport, without heavy `scroll` handlers.** An observer is created with `new IntersectionObserver(callback, options)`, where `root` sets the container, `rootMargin` the offsets and `threshold` the visible fraction that triggers the callback. The callback receives an array of `IntersectionObserverEntry` objects with `isIntersecting`, `intersectionRatio` and `target`. Typical uses: lazy loading images, scroll-triggered animations, view analytics. ```javascript const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) entry.target.classList.add('visible'); }); }); observer.observe(document.querySelector('#target')); ``` **Key point:** it is a "visibility watcher" that replaces `onscroll` and `getBoundingClientRect()` and works asynchronously, with almost no CPU cost.Shown above the full answer for quick recall.Answer (EN)Image**`IntersectionObserver` is a built-in browser API that lets you track when an element enters or leaves the viewport, without heavy `scroll` handlers.** It is an object that "watches" an element and reports when it intersects the viewport or another container. ## Theory ### TL;DR - `IntersectionObserver` tracks the intersection of an element with the viewport or a given container. - It replaces `scroll` events, manual `getBoundingClientRect()` checks and `window.onscroll`. - Creation: `new IntersectionObserver(callback, options)` plus `observer.observe(element)`. - Options: `root`, `rootMargin`, `threshold`. - The callback receives an array of `IntersectionObserverEntry` objects with `isIntersecting`, `intersectionRatio`, `target`. - Stopping: `observer.unobserve(target)` for one element, `observer.disconnect()` for all of them. ### Quick example ```javascript const target = document.querySelector('#target'); const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { console.log('The element entered the viewport!'); } else { console.log('The element left the viewport!'); } }); }); observer.observe(target); // start observing ``` ### A simple example on a page ```html <div style="height: 120vh;"></div> <div id="target" style="height: 100px; background: coral;">I am the target</div> <div style="height: 120vh;"></div> <script> const target = document.querySelector('#target'); const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { console.log('The element entered the viewport!'); } else { console.log('The element left the viewport!'); } }); }); observer.observe(target); // start observing </script> ``` When you scroll the page and the block reaches the screen, the console prints: ```text The element entered the viewport! ``` This is an excellent alternative to: - `scroll` events; - manual checks with `getBoundingClientRect()`; - `window.onscroll` handlers, which put a heavy load on the page. ### Constructor parameters ```javascript new IntersectionObserver(callback, options); ``` | Parameter | What it does | | --- | --- | | `callback` | The function called on every intersection change | | `options.root` | The container to track inside (the browser window by default) | | `options.rootMargin` | Offsets (for example `'0px 0px -100px 0px'`), like CSS `margin` | | `options.threshold` | The trigger threshold, the visible fraction of the element (from 0 to 1) | An example with options: ```javascript const observer = new IntersectionObserver(callback, { root: null, // the browser window rootMargin: '0px 0px -100px 0px', // fires a little earlier threshold: 0.5 // when 50% of the element is visible }); ``` ### The entry object in the callback Every element the observer watches is represented by an `IntersectionObserverEntry` object: ```javascript entry.isIntersecting; // true / false entry.intersectionRatio; // the visible fraction (from 0 to 1) entry.boundingClientRect; // the element's position entry.rootBounds; // the bounds of the root area entry.target; // the element itself ``` The callback receives an array of such entries because one observer can watch hundreds of elements, and the browser delivers all the changes of a tick together. ### Practical examples Elements fading in lazily: ```javascript const items = document.querySelectorAll('.fade'); const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add('visible'); } }); }); items.forEach((item) => observer.observe(item)); ``` ```css .fade { opacity: 0; transform: translateY(20px); transition: 0.5s; } .fade.visible { opacity: 1; transform: translateY(0); } ``` The elements float in smoothly as they reach the screen. Lazy loading of images: ```html <img data-src="photo.jpg" alt="photo" width="400" height="300" /> ``` ```javascript const images = document.querySelectorAll('img[data-src]'); const observer = new IntersectionObserver((entries, obs) => { entries.forEach((entry) => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; obs.unobserve(img); // stop watching this image } }); }); images.forEach((img) => observer.observe(img)); ``` Now images load **only when they become visible**, which saves bandwidth and speeds up the site. ### Advantages over scroll, and controlling the observer | A plain scroll handler | IntersectionObserver | | --- | --- | | Fires on **every pixel** of scrolling | Works asynchronously and efficiently | | You compute `getBoundingClientRect()` by hand | It does the maths for you | | CPU load | Almost no load | | Awkward with many elements | One observer watches hundreds of elements | To stop watching one particular element: ```javascript observer.unobserve(target); ``` To shut the observer down completely: ```javascript observer.disconnect(); ``` Summary: | What it does | `IntersectionObserver` | | --- | --- | | Tracks | When an element enters or leaves the viewport | | Replaces | `onscroll` and `getBoundingClientRect()` | | Used for | Lazy loading, scroll animations, view analytics | | Performance | Very high, the browser does the maths, not your JS code | > **Just remember:** `IntersectionObserver` is a "visibility watcher". It tells you when an element appeared on screen, and it does so efficiently, without extra `scroll` events. ### Common mistakes - Forgetting to call `observer.observe(element)`. An observer on its own watches nothing. - Expecting the callback only when the element appears. It also fires when the element leaves, so the `entry.isIntersecting` check is mandatory. - Not calling `unobserve()` after a one-off action (loading an image, say) and redoing the work on every intersection. - Confusing `threshold: 0` with `threshold: 1`: the first fires on the element's first pixel, the second only when the whole element is visible. - Writing `rootMargin` in percentages where pixels are needed, or without units at all. A unitless value is rejected by the browser. - Passing a `root` that is not an ancestor of the target. Observation then does not behave as expected. - Not shutting the observer down when the component is destroyed (`disconnect()`), which leaks memory and keeps callbacks for removed nodes.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.