When I need to detect that the user has scrolled to a specific element, I usually turn to Waypoints. It’s a handy library that lets you detect the scroll direction as well as the presence of the element.

However, Waypoints isn’t perfect. If you need to detect elements that are ever styled with display: none; it quickly starts throwing a tantrum. Sometimes you can get around this by switching to opacity instead of display.

Sometimes you can’t.

Recently, I had a group of hidden elements that needed to animate when the user scrolled to them. Their parent container started out hidden, and was shown when the user pressed a toggle button. This meant the elements basically needed two hidden states: the first one when their parent container was hidden (using display: none;) and the second after the parent had been shown, but before the elements had been scrolled and animated.

Since I was initially using display: none; on the parent, it became incredibly difficult to use Waypoints for the child elements. I tried initializing the elements with Waypoints only after the toggle was pressed and the parent container lost display: none; but was unsuccessful.

My solution was to borrow this code from the excellent site, Go Make Things. This uses plain JS to detect if an element is within the viewport:

function isInViewport (elem, offset = 0) {
    const bounding = elem.getBoundingClientRect();
    return (bounding.top >= 0 && bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) + offset);

I attached a listener to the scroll event to fire this function when the user scrolls. If the element we want to animate is in the viewport, we can then add a class to trigger our animation.