Search code examples
javascripthtmlcssscrolldom-events

Why event listener on scroll not working on window


I have a main layout block with the property overflow: auto and because of this, the scroll event is triggered only in this block, although I add a listener to the window object.

How do I add a scroll event listener to the window if I don't know the class or id of the specific block on which the event occurs?

window.addEventListener('scroll', () => {
  console.log('scroll!')
});
* {
  margin: 0;
  padding: 0;
}

main {
  height: 100vh;
  overflow-y: auto;
}

div {
  width: 500px;
  height: 300px;
  margin: 10px 0;
  background: rgb(45, 62, 206);
}
<main>
  <div></div>
  <div></div>
  <div></div>
  <div></div>
</main>


Solution

  • I'm with Rory, this seems like it could be an X/Y problem. But answering the question asked:

    scroll events don't bubble, but you can still handle them on ancestor elements by adding a listener for the capture phase (true as a third argument to addEventListener). The capture phase occurs even for events that don't bubble:

    window.addEventListener('scroll', () => {
      console.log('scroll!')
    }, true); // <===
    

    Live Example:

    window.addEventListener('scroll', () => {
      console.log('scroll!')
    }, true);
    * {
      margin: 0;
      padding: 0;
    }
    
    main {
      height: 100vh;
      overflow-y: auto;
    }
    
    main div {
      width: 500px;
      height: 300px;
      margin: 10px 0;
      background: rgb(45, 62, 206);
    }
    <main>
      <div></div>
      <div></div>
      <div></div>
      <div></div>
    </main>

    This diagram from the DOM UI Events specification is helpful for understanding capture vs. target vs. bubbling phases:

    Graphical representation of an event dispatched in a DOM tree using the DOM event flow, showing the event starting in Window and passing through Document and html, body, and a series of other ancestor elements during the capturing phase, being in the target phase at the target element, and then working its way back from the element to Window in the bubbling phase.

    In events that don't bubble, it's just the bubbling phase, not the capture phase, that doesn't happen.


    (In the runnable example I made a small change to the CSS that's unrelated to the answer; it's just so the Stack Snippet works better. The Stack Snippet console is a div inside the document, so it was being affected by your div rule. I just changed the rule's selector to be main div instead so it only applied to the elements within main.)