What's challenging with Intersection Observer v1?

To be clear, Intersection Observer v1 is great, but it's not perfect. There are
some corner cases where the API falls short. Let's have a closer look!
The Intersection Observer v1 API can perfectly tell you when an element is scrolled into the
window's viewport, but it doesn't tell you whether the element is covered
by any other page content (that is, when the element is occluded) or whether
the element's visual display has been modified by visual effects like transform, opacity,
filter, etc., which effectively can make it invisible.

Now while for an element in the top-level document this information can be determined by analyzing
the DOM via JavaScript, for example via
DocumentOrShadowRoot.elementFromPoint()
and then digging deeper, the same information cannot be obtained if the element in question is
located in a third-party iframe.

Why is actual visibility such a big deal?

The Internet is, unfortunately, a place that attracts bad actors with even worse intentions.
For example, a shady publisher that serves pay-per-click ads on a content site might be incentivized
to trick people into clicking their ads to increase the publisher's ad payout (at least
for a short period, until the ad network catches them).
Typically, such ads are served in iframes.
Now if the publisher wanted to get users to click such ads, they could make the ad iframes
completely transparent by applying a CSS rule iframe { opacity: 0; } and overlaying the iframes
on top of something attractive, like a cute cat video that users would actually want to click.
This is called clickjacking.
You can see such a clickjacking attack in action in the upper section of this
demo (try "watching" the 🐈 cat video
and ☑️ activate "trick mode").
You will notice that the ad in the iframe "thinks" it received legitimate clicks, even if it was
completely transparent when you (pretendedly involuntarily) clicked it.

How does Intersection Observer v2 fix this?

Intersection Observer v2 introduces the concept of tracking the actual "visibility" of a target
element as a human being would define it.
By setting an option in the
IntersectionObserver constructor,
intersecting
IntersectionObserverEntrys
(pardon the wrong plural ending here) will then contain a new boolean field named isVisible.
A true value for isVisible is a strong guarantee from the underlying implementation
that the target element is completely unoccluded by other content
and has no visual effects applied that would alter or distort its display on screen.
In contrast, a false value means that the implementation cannot make that guarantee.

An important detail of the
spec
is that the implementation is permitted to report false negatives (that is, setting isVisible
to false even when the target element is completely visible and unmodified).
For performance or other reasons, implementations should limit themselves to working with bounding
boxes and rectilinear geometry; they shouldn't try to achieve pixel-perfect results for
modifications like border-radius.

That said, false positives are not permitted under any circumstances (that is, setting
isVisible to true when the target element is not completely visible and unmodified).

Warning: Visibility is much more expensive to compute than intersection. For that reason,
Intersection Observer v2 is not intended to be used broadly in the way that
Intersection Observer v1 is. Intersection Observer v2 is focused on combatting fraud
and should be used only when Intersection Observer v1 functionality is truly insufficient.

What does the new code look like in practice?

The IntersectionObserver constructor now takes two additional configuration properties: delay
and trackVisibility.
The delay is a number indicating the minimum delay in milliseconds between notifications from
the observer for a given target.
The trackVisibility is a boolean indicating whether the observer will track changes in a target's
visibility.

⚠️ It's important to note here that when trackVisibility is true, delay is required to be at
least 100 (that is, no more than one notification every 100ms).
As noted before, visibility is expensive to calculate, and this requirement is a precaution against
performance degradation (and battery consumption). The responsible developer will use the
largest tolerable value for delay.

If the observer's trackVisibility attribute is false, then the target is considered visible.
This corresponds to the current v1 behavior.

If the target has an effective transformation matrix other than a 2D translation
or proportional 2D upscaling, then the target is considered invisible.

If the target, or any element in its containing block chain, has an effective opacity other than
1.0, then the target is considered invisible.

If the target, or any element in its containing block chain, has any filters applied,
then the target is considered invisible.

If the implementation cannot guarantee that the target is completely unoccluded by other page
content, then the target is considered invisible.

This means current implementations are pretty conservative with guaranteeing visibility.
For example, applying an almost unnoticeable grayscale filter like filter: grayscale(0.01%)
or setting an almost invisible transparency with opacity: 0.99 would all render the element
invisible.

Below is a short code sample that illustrates the new API features. You can see this click tracking
logic in action in the second section of the demo
(but now, try "watching" the 🐶 puppy video). Be sure to activate "trick mode" again to immediately
convert yourself into a shady publisher and see how Intersection Observer v2 prevents
non-legitimate ad clicks from being tracked.
This time, Intersection Observer v2 has our back! 🎉

Note: Different from typical lazy-loading code, if you use Intersection Observer to prevent this
kind of clickjacking attacks, you must notunobserve the element after the first intersection.

<!DOCTYPE html>
<!-- This is the ad running in the iframe -->
<button id="callToActionButton">Buy now!</button>