Trigger in View - A simple intersection observer helper
TLDR; Trigger-in-view is a little helper library I wrote that triggers after scroll and runs a callback against only targeted elements visible in a specified container.
Triggering actions when certain DOM elements comes into the view is a common requirement in web development. A common usage is lazy-loading images, the deferring of loading full resolution images until they'll be seen by the user. But, there are many other uses and can form the foundation of a storytelling narrative such as A Year in Lockdown, used to trigger video playing and animations.
Recently, I needed to list numerous items in a scrollable view. Each item contained an image and without any additional logic the app would load all 300+ images on start, even if the user never viewed them. Scrolling lists present a unique challenge, especially on mobile, as the user is likely you quickly swipe the list searching for the item they're looking for.
The usual solution is to use IntersectionObserver
and trigger
the image load on the intersection event. The problem with this simple approach
is it triggers all items as they whizz by as the user flicks the scrolling list
with their thumb.
You could wrap the IntersectionObserver
callback in a debounce
function, but
there's a problem with that. Intersection events happen one at a time as each
observed item comes into view. Debouncing the callback means only one item
will be processed with the callback.
What is needed is a way to delay triggering the IntersectionObserver
callback
that will also recognize observed items currently in view. The answer is to take
advantage of a trait of the IntersectionObserver.observe()
method that
triggers the callback immediately when used on an element.
By looping over all observed items and adding them to a new
IntersectionObserver
, the currently visible items can be processed and removed
if desired. The IntersectionObserver
can then .disconnected()
ready for the
next go around.
The creation -> loop -> disconnect
function is attached to the list's scroll
event using a simple setTimeout
debounce function. Now, the list can be
scrolled as fast as the user wants without any unnecessary loads.
One limitation to this approach is the intersection check only happens once the
setTimeout
delay has passed, and that is dependant upon scroll events. The
browser's momentum scrolling behaviour can result in a fast flick of the list
taking a long time to slow down and stop. Reducing the delay length helps, but
increases the chance of unnecessary loads. A smarter way would be to count the
frequency of the scroll events and trigger the check once it has passed a
certain threshold. Maybe I'll add that to a future version.
I wrote the library mainly for myself but I took the opportunity to learn how to publish a namespaced package onto NPM. So, for anyone interested the code is viewable on github.