Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to capture all scrolling events on a page without attaching an onscroll handler to every single container

Consider the following Web page:

 <html>    <body onscroll="alert('body scroll event')">      <div style='width:200px;height:200px;overflow:auto' onscroll="alert('div scroll event')">        <div style='height:400px'>        </div>      </div>    </body>  </html> 

This html creates a div with a scrollbar. If you move the scrollbar, the onscroll event on the div element is triggered. However, the onscroll event on the body is NOT fired. This is expected, since the W3C states that element onscroll events do not "bubble".

However, I'm developing a client-side web framework that needs to know any time a scroll bar on ANY element of the page is scrolled. This would be easy to do if onscroll bubbled, but unfortunately it does not. Is there any other way to detect onscroll events across an entire page? (Right now I'm focusing mainly on Webkit, so a Webkit-specific solution would be fine...)

Here are some things I've tried:

  1. Capturing DOMAttrModified (doesn't seem to fire for moving scrollbars.)
  2. Using DOM Observers (also don't seem to fire for scrollbars)
  3. Changing the onscroll event type to bubble (seems to not be possible)

It seems the ONLY way to capture onscroll events globally is to attach an onscroll event to EVERY element that may scroll, which is very ugly and is going to hurt the performance of my framework.

Anyone know a better way?

like image 242
drcode Avatar asked Nov 01 '12 20:11

drcode


People also ask

Does scroll event bubble?

The scroll event does not bubble up. Although the event does not bubble, browsers fire a scroll event on both document and window when the user scrolls the entire page.

How do I add a scroll event listener to a div?

forEach(element => { window. addEventListener( "scroll", () => runOnScroll(element), { passive: true } ); }); Or alternatively, bind a single scroll listener, with evt => runOnScroll(evt) as handler and then figure out what to do with everything in elements inside the runOnScroll function instead.


2 Answers

The simplest way to detect all scroll events in modern browser would be using 'capturing' rather than 'bubbling' when attaching the event:

window.addEventListener('scroll', function(){ code goes here }, true) 

Unfortunately as I am aware there is no equivalent in older browser such as <= IE8

like image 147
Norman Xu Avatar answered Oct 10 '22 23:10

Norman Xu


I had this same issue.

The easiest way of course is to use jQuery. Be aware that this method could potentially slow down your page significantly. Also it will not account for any new elements that are added after the event is bound.

$("*").scroll(function(e) {     // Handle scroll event }); 

In vanilla JavaScript, you can set the useCapture boolean to true on your addEventListener call, and it will fire on all elements, including those added dynamically.

document.addEventListener('scroll', function(e) {     // Handle scroll event }, true); 

Note though that this will fire before the scroll event actually happens. As I understand it, there's two phases events go through. The capture phase happens first, and starts from the page root (ownerDocument?) and traverses down to the element where the event happened. After this comes the bubbling phase, which traverses from the element back up to the root.

Some quick testing too showed that this may be how jQuery handles it (for tracking scrolls on all page elements at least), but I'm not 100% sure.

Here's a JSFiddle showing the vanilla JavaScript method http://jsfiddle.net/0qpq8pcf/

like image 45
Conner H Avatar answered Oct 10 '22 21:10

Conner H