The “Intersection Observer” provides a way to asynchronously observe changes in the intersection (overlapping) of elements. This can be in relation to other elements, or the viewport. In this article we’ll take a look at a few demos and discuss the relevance that Intersection Observer will play in the future for web developers. I’ll also share code examples to help get you started for those late night experimentation sessions. Let’s dive in!
What Can the Intersection Observer Do?
The IntersectionObserver
API lets you register a callback function which is executed whenever an element being monitored enters or exits another element, or the viewport.
There are plenty of ideas and suggestions shared in this W3C explainer doc on GitHub, however, Intersection Observer can be handy for natively building out features such as:
- Lazy Loading
- Infinite Scrolling
- Reporting Visibility/Location
- Executing Animations
Check out this demo by Dan Callahan to clarify what we’re talking about:
To get started with IntersectionObserver
let’s explore the appropriate coding steps required. You can always make sure your intended browsers/devices support the IO API by referencing caniuse. We’ll begin by creating an observer and discussing how it can be used to monitor components.
Creating an Observer
IntersectionObserver
starts by requiring a group of options defined as an object literal and passed as an argument to your defined observer object.
let options = { root: null, // relative to document viewport rootMargin: '0px', // margin around root. Values are similar to css property. Unitless values not allowed threshold: 1.0 // visible amount of item shown in relation to root }; let observer = new IntersectionObserver(callback, options);
These observer options on lines 2-4 will dictate some important details when it comes to detecting the visibility of a target element in relation to the root. The first argument of the observer object (last line) represents a callback (function) that’s executed as requirements are met by your observer. The second argument refers to our object literal containing the observer’s options and accepts the following properties:
root
: The element you want to test the intersection against. A value ofnull
refers to the browser’s viewport. You can also pass in DOM selector methods such asdocument.querySelector('#mytargetobject')
.rootMargin
: If you need to expand or shrink the effective size of the root element before computing intersections. These values passed are similar to the CSSmargin
property. If theroot
element is specified, the values can be percentages.threshold
: Either a single number or an array of numbers indicating what percentage of the target’s visibility triggers the observer’s callback. A value of 1.0 means the threshold isn’t considered passed until every pixel is visible, whereas 0 means the element is out of view completely.
As I previously mentioned, you’ll handle the callback argument by creating a function containing custom logic based on your project’s needs. This logic is executed anytime an observed element(s) is visible in relation to the defined root element.
function onChange(changes, observer) { // logic goes here } let observer = new IntersectionObserver(onChange, options);
Logic within your observer’s function can be events such as loading images, adding/removing classes, or controlling visibility, but the choice is yours as to what can be done from within depending on your needs and goals.
Every time the observer detects a change, a changes
event is reported (kind of like a function()
reporting an events object) from the observer callback. By using this triggered event, we can check for visibility of our element in relation to the root before running any additional logic by detecting properties on the change
event. Some developers will replace changes
with the word entries
instead, but both approaches work the same.
function onChange(changes, observer) { changes.forEach(change => { if (change.intersectionRatio > 0) { // your observer logic } }); }
This loop states “For each change detected, check to see if the target element is currently visible (greater than 0) in relation to the root defined.” The intersection ratio assists in reporting how much of the element is visible using a value between 0.0 (not visible) and 1.0 (fully visible). You can think of intersection ratio just like the threshold
property defined in your observer’s options.
The Observe Method
So far we’ve created an options object, a callback function and defined an observer object, but we still don’t have anything to observe. This is where the observe method will come in service.
let images = document.querySelectorAll('img'); images.forEach(img => observer.observe(img));
This method adds an element to the set of target elements being watched by the IntersectionObserver
. In this example I’m observing every image on the page by the results obtained from the images
selector reference. The observe()
method will continue monitoring a target until any of the following occurs: the unobserve()
or disconnect()
methods are called along with the target element or intersection root deleted. If you no longer need to observe an element it’s best to make a call to unobserve()
and pass in your target as the argument.
Support Condition
If you need to feature test the support of this API you can wrap everything in an if
/else
conditional statement like so:
if ('IntersectionObserver' in window) { // supported } else { // not supported }
Since the Intersection Observer API still resides at a working draft stage I encourage you to feature detect the window
object, or you can also find polyfills if you prefer.
Live Examples
The following demo contains all the code I’ve discussed thus far with some additional tweaks. This demo lazy loads images when they’re displaying 50% of their visibility in relation to the viewport.
You might notice a differing behavior when it comes to the picture
elements compared to the img
elements in Chrome and Firefox. Both browsers load picture
elements perfectly, but picture
disregards our threshold even with absolute size constraints defined. They appear to load when they’re about 10% in view vs. the 50% threshold defined in the demo. Leave a comment below if you notice this oddity, or have experienced issues with IntersectionObserver
and picture
specifically.
Here’s another demo creating an infinite scrolling scenario that lazy loads imagery using Ajax to load imagery as needed.
In this scenario, I’m inserting a sentinel to the DOM as my observed target. This sentinel is placed next to the last item within the infinite scroller, and as the sentinel comes into view, the callback loads the data, creates the next item(s), attaches them to the DOM and repositions the sentinel. If you properly recycle the sentinel, no additional calls to observe()
are required. Here’s a great diagram by the Google Developer team to help visually explain this approach.
A Note on Imagery
When an img
element with a constraint of max-width: 100%
is observed, it will fail to load. That means any imagery loaded in a lazy fashion must have constraints defined in the CSS or inline accordingly The problem with picture
elements lacking any constraints is that there’s zero size before their content is loaded; meaning that they all intersect the viewport simultaneously as you scroll. I suspect getBoundingClientRect()
is part of the reason as this is one of the primary methods to obtain an element’s coordinates and constraints.
Conclusion
Are you using IntersectionObserver
in your own work right this minute? Are you excited for this feature and the possibilities it brings? Could IntersectionObserver
replace the need for event-based features such as position: sticky
? Personally I feel this new API is a solid addition to our specifications and I eagerly look forward to its continued growth over the coming years.
I’ve included some useful links below and encourage you to dive deeper in your free time. Each link will help you gain a better understanding of how this new API works and functions. If you have any tips or tricks for other readers leave them in the comments below. As always, happy coding!
Links
- LazyLoad (vanilla-lazyload)
- Intersection Observer Examples
- Lazy loading images using intersection observer by Dean Hume
- IntersectionObserver Sample
- Intersection Observer API on mozilla.org
- Lazy Loading Images with Intersection Observer by Cory Dowdy
- Using the Intersection Observer API to Trigger Animations and Transitions on Alligator.io
- IntersectionObserver’s Coming into View by Surma
No comments:
Post a Comment