Intersection Observer
is one of those APIs that's probably universally loved, and it's usable in all
major browsers. Developers have used this API for a wide range of use cases,
including
lazy loading images and videos,
notifications when elements reach position: sticky,
firing analytics events,
and many more.
In its most basic form, this is what the Intersection Observer v1 API looks like:
const onIntersection = (entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
console.log(entry);
}
}
};
const observer = new IntersectionObserver(onIntersection);
observer.observe(document.querySelector('#some-target'));
Visibility challenges in Intersection Observer v1
With the Intersection Observer v1 API, you can learn when an element is
scrolled into the window's viewport. However, you can't determine if that
element is covered by other page content, known as occlusion, or if the
element appear modified by CSS, such as transform, opacity, or filter,
which may make the element it invisible.
For an element in the top-level document, this information can be determined by
analyzing the DOM with JavaScript, for example with
DocumentOrShadowRoot.elementFromPoint().
In contrast, the same information cannot be obtained if the element in question is
located in a third-party iframe.
Why is visibility important?
Unfortunately, the internet has bad actors. For example, a dishonest publisher might use pay-per-click ads on a website. They might trick users into clicking these ads to earn more money, at least until the ad network discovers their scheme. Typically, such ads are served in iframes.
To trick users, the publisher could make the ad iframes completely transparent
with CSS: iframe { opacity: 0; }. Then, they could place these transparent
iframes over appealing content, like a cute cat video, that users want to click.
This is called clickjacking.
You can see such a clickjacking attack in action in the upper section of our demo. Try "watching" the cat video and activate trick mode. The ad in the iframe registers clicks as legitimate, even if you (unintentionally) clicked it while the iframe was transparent.

Improvements in Intersection Observer v2
Intersection Observer v2 can track an element's "visibility" as a human
would define it. If you set an option in the IntersectionObserver
constructor,
the resulting
IntersectionObserverEntry
instances include a new boolean field called isVisible. When isVisible
is true, the browser makes sure the element is completely uncovered by other
content and has no visual effects that hide or change its display. If
isVisible is false, the browser cannot make that guarantee.
The
spec
allows false negatives: isVisible can be false even when the element is
truly visible and unchanged. For performance, browsers use simpler calculations,
such as bounding boxes and rectangular shapes, and don't check every pixel for
complex details like border-radius.
However, false positives are not permitted under any circumstances. This
means isVisible won't be true if the element is not completely visible
and unmodified.
Apply these changes
The IntersectionObserver constructor now takes two additional configuration
properties:
delayis a number that indicates the minimum delay in milliseconds between notifications from the observer, for a given target.trackVisibilityis a boolean to indicate if the observer will track changes in a target's visibility.
When trackVisibility is true, delay must be set to 100 or a higher value
(that is, no more than one notification every 100ms).
As visibility is expensive to calculate, this is a precaution against
performance degradation and battery consumption. Responsible developers should
use the largest tolerable value for delay.
The spec,
calculates visibility. Like with version 1, when observer's trackVisibility
attribute is false, the target is considered visible
In version 2, the target is considered invisible if:
It has an effective transformation matrix, other than a 2D translation or proportional 2D upscaling.
The target, or any element in its containing block chain, has an effective opacity smaller than 1.0.
The target, or any element in its containing block chain, has any filters applied.
If the implementation cannot guarantee that the target is completely unoccluded by other page content.
This means current implementations are pretty conservative in guaranteeing
visibility. For example, applying a nearly unnoticeable grayscale filter
(filter: grayscale(0.01%)) or setting the smallest transparency
(opacity: 0.99) would render the element invisible.
Here is a code sample that illustrates the new API features. You can see its click tracking logic in action in the second section of the demo Try "watching" the puppy video. Activate trick mode to convert yourself into a bad actor and see how Intersection Observer v2 prevents non-legitimate ad clicks from being tracked. Intersection Observer v2 protects us.

<!DOCTYPE html>
<!-- This is the ad running in the iframe -->
<button id="callToActionButton">Buy now!</button>
// This is code running in the iframe.
// The iframe must be visible for at least 800ms prior to an input event
// for the input event to be considered valid.
const minimumVisibleDuration = 800;
// Keep track of when the button transitioned to a visible state.
let visibleSince = 0;
const button = document.querySelector('#callToActionButton');
button.addEventListener('click', (event) => {
if ((visibleSince > 0) &&
(performance.now() - visibleSince >= minimumVisibleDuration)) {
trackAdClick();
} else {
rejectAdClick();
}
});
const observer = new IntersectionObserver((changes) => {
for (const change of changes) {
// ⚠️ Feature detection
if (typeof change.isVisible === 'undefined') {
// The browser doesn't support v2, fallback to v1 behavior.
change.isVisible = true;
}
if (change.isIntersecting && change.isVisible) {
visibleSince = change.time;
} else {
visibleSince = 0;
}
}
}, {
threshold: [1.0],
// 🆕 Track the actual visibility of the element
trackVisibility: true,
// 🆕 Set a minimum delay between notifications
delay: 100
}));
// Require that the entire iframe be visible.
observer.observe(document.querySelector('#ad'));
Additional resources
- Working Draft of the Intersection Observer.
- Intersection Observer v2 on Chrome Platform Status.
Acknowledgements
Thanks to Simeon Vincent, Yoav Weiss, and Mathias Bynens for reviewing, as well as Stefan Zager for reviewing and implementing the feature in Chrome.