Determine if a section is in view
I am especially unsure about looping over the sections in a scroll event.


There doesn't seem to be much of an alternative. One could select all elements with class name is-visible:

const visible = document.getElementsByClassName('is-visible');


Note that that collection is live, meaning it will be updated dynamically as the DOM changes, so there isn't any reason to re-query the DOM for those.

In the version that attempts to debounce the DOM updates, the IIFEs seem a bit excessive. Is the goal to limit scope of variables?



The function passed to panels.forEach() could be simplified:


panel =&gt; {
    if(scrollTop &gt;= panel.offsetTop - ( windowHeight / 2)){
        panel.classList.add("is-visible");
    } else {
        panel.classList.remove("is-visible");
    }
    if(scrollTop &gt;= (panel.offsetTop + panel.clientHeight) - ( windowHeight / 2) ) {
        panel.classList.remove("is-visible");
    }
}



This can be simplified by calling Element.classList.toggle() with two arguments.

panel =&gt; {
    let addClass = scrollTop &gt;= panel.offsetTop - ( windowHeight / 2);

    if(scrollTop &gt;= (panel.offsetTop + panel.clientHeight) - ( windowHeight / 2) ) {
        addClass = false;
    }
    panel.classList.toggle("is-visible", addClass);
}





const panels = document.querySelectorAll(".panel");



In most browsers it would generally be quicker to fetch the game squares using document.getElementsByClassName(), but that would return a live collection (refer to this SO answer for an explanation). 

 const panels = [...document.getElementsByClassName("panel")];




The debounced version looks a little bit un-efficient because it uses an interval so that function gets executed more than it needs to. 

There are also many arguments not to use setInterval and setTimeout - e.g. blindman67's SO Post Why is requestAnimationFrame better than setInterval or setTimeout

The functionality from the setInterval callback could be put into a function called checkVisibility. Then the scroll handler could be simplified to use requestAnimationFrame()

let timeout;
window.addEventListener("scroll", _ =&gt; {
    if (timeout) {
        window.cancelAnimationFrame(timeout);
    }
    timeout = window.requestAnimationFrame(checkVisibility);
});