Determining the direction of IntersectionObserver events
I had a situation where I wanted to visually introduce a footer element recently when a certain element was passed in the viewport. Instead of using scroll events I had opted to use the newer IntersectionObserver (finally we are getting broad browser support thanks to iOS 12.2 and Safari in macOS 10.14.4).
Anyway, there are some good tutorials already on the fundamentals of the IntersectionObserver. WebKit’s own https://webkit.org/blog/8582/intersectionobserver-in-webkit/ being one good example.
However, what doesn’t seem well covered is dealing with the direction of how the IntersectionObserver has been triggered.
I solved this by comparing the boundingClientRect.y
properties of multiple IntersectionObserver events. Perhaps there is a better way?
In short, my problem was that after observing something on the page I wanted to visually introduce a new element. However, I wanted the new element to stay visible, even if the observed element was no longer visible but the user was still ‘past’ the trigger element. If the user scrolled back up past the element I wanted to remove the element from view.
Anyway, that probably sounds more complicated than it needs to be. Take a look at the reduction here: https://codepen.io/benfrain/pen/GzXwgr
See the Pen
Determine IntersectionObserver direction by Ben Frain (@benfrain)
on CodePen.
In terms of code, we create a new empty array in the global scope (or at least outside of where the function is fired). Then on each event the array gets populated by the boundingClientRect.y
value of that event.
We then create a new sliced copy of that array with just the last two items of the main array and compare the last and next to last entries of the array. If the next to last was greater than the last entry we know we are heading down.
With this I was able to write a function to accommodate my needs. I’ve included all the IntersectionObserver stuff here for the sake of completeness:
var triggerElementPositions = new Array();
var triggerElem = document.querySelector(".your-TriggerElement");
var options = {
root: null,
rootMargin: "0px",
threshold: [0, 0.5, 0.75, 1],
};
var observeFooter = new IntersectionObserver(showTutorialFooter, options);
observeFooter.observe(triggerElem);
function showTutorialFooter(e) {
triggerElementPositions.push(e[0].boundingClientRect.y);
let compareArray = triggerElementPositions.slice(triggerElementPositions.length - 2, triggerElementPositions.length);
let down = compareArray[0] > compareArray[1] ? true : false;
if (!down) {
dashDepositors.setAttribute("data-footer-visible", "false");
} else {
if (e[0].intersectionRatio > 0.5) {
dashDepositors.setAttribute("data-footer-visible", "true");
}
}
}
Essentially, if a user is scrolling up the page (or more accurately the value of the next to last position is less than the last), we set the footer to hide by default. If the user is scrolling down and the intersection is greater than 50%, we show the footer.
Since you only really care about the last “y” value, it’s not necessary to use an array. For a very broad threshold array, this would quickly fill up the array and cause a memory leak in the application.
// defined above IntersectionObserver callback
let lastTriggerPosition;
// inside callback
const down = lastTriggerPosition !== undefined ? lastTriggerPosition > y : true;
lastTriggerPosition = y;
The first condition checks if the value is undefined, and if it is we can assume it hasn’t been triggered before and therefore the direction is “down”.