60

Is there a js listener for when a user scrolls in a certain textbox that can be used? Kinda like onclick except for scrolling. I saw HTML5 event listener for number input scroll - Chrome only but that seems to be for chrome only. I'm looking for something cross-browser.

Community
  • 1
  • 1
Kpower
  • 1,237
  • 3
  • 11
  • 19

6 Answers6

135

For those who found this question hoping to find an answer that doesn't involve jQuery, you hook into the window "scroll" event using normal event listening. Say we want to add scroll listening to a number of CSS-selector-able elements:

// what should we do when scrolling occurs
function runOnScroll(element) {
  // not the most exciting thing, but a thing nonetheless
  console.log(element);
};

// grab elements as array, rather than as NodeList
const elements = document.querySelectorAll(`...`);

// and then make each element do something on scroll
elements.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.

Note that we're using the passive attribute to tell the browser that this event won't interfere with scrolling itself. This is important if we want smooth scrolling behaviour (and also means we shouldn't do perform reflow-triggering DOM updates during scroll).

For bonus points, you can give the scroll handler a lock mechanism so that it doesn't run if we're already scrolling:

// global lock, so put this code in a closure of some sort so you're not polluting.
let locked = false;
let lastCall = false;

function runOnScroll(element) {
  if(locked) return;

  if (lastCall) clearTimeout(lastCall);
  lastCall = setTimeout(() => {
    runOnScroll(element);
    // you do this because you want to handle the last
    // scroll event, even if it occurred while another
    // event was being processed.
  }, 200);

  // ...your code goes here...

  locked = false;
};
Mike 'Pomax' Kamermans
  • 49,297
  • 16
  • 112
  • 153
  • Just curious - and not sure if it matters - but should useCapture return false or true for the scroll event listener? – beefchimi Sep 14 '14 at 21:08
  • 1
    doesn't matter - setting `useCapture` is a way to ensure the handler trigger before any handlers get the event due to bubbling or "I don't care about the order it's called in" by not setting `useCapture`. It's not too useful for that, however, as there's not call order guarantee when multiple listeners with `useCapture=true` are used. – Mike 'Pomax' Kamermans Sep 15 '14 at 15:08
  • 2
    Very clean, but doesn't this kill scroll performance since we're adding a listener on scroll by however many elements we're selecting? I would think we would want to centralize onScroll logic so we can manage what executes. What have you found? – gdbj Nov 06 '16 at 18:50
  • Very much so, so for performance you can turn this into a standard "event listener only sets a flag" (where the 'flag' is just the scroll target value), with an independently scheduled "handle scroll data" function that you call on a timeout (if it's not running, `scroll` event starts it up, if it's already running, it cancels its run if it sees no new scroll data to handle). – Mike 'Pomax' Kamermans Nov 06 '16 at 21:47
  • Regarding the locked true/false check: won't the code only fire once (the first time the user scrolls) ?? – Kalnode Apr 05 '18 at 01:47
  • the scroll event fires *so often* that this is effectively irrelevant. It will run, then stop, then run again if scroll gets fired again, which it will. You can of course make the lock more elaborate, by, prior to returning, setting a value that indicates "run one more time, if we're not getting retriggered". The code for that's fairly easy, but I'll leave it as exercise for the reader because that's no longer really related to this question as a general event queue handling approach. – Mike 'Pomax' Kamermans Apr 05 '18 at 03:46
  • Help me understand this. You've got a `function(element)` that takes `element` but that variable is never used? you're always adding the eventlistener to `window` – oliversisson Aug 26 '21 at 09:13
6

I was looking a lot to find a solution for sticy menue with old school JS (without JQuery). So I build small test to play with it. I think it can be helpfull to those looking for solution in js. It needs improvments of unsticking the menue back, and making it more smooth. Also I find a nice solution with JQuery that clones the original div instead of position fixed, its better since the rest of page element dont need to be replaced after fixing. Anyone know how to that with JS ? Please remark, correct and improve.

<!DOCTYPE html>
<html>

<head>
<script>

// addEvent function by John Resig:
// http://ejohn.org/projects/flexible-javascript-events/

function addEvent( obj, type, fn ) {

    if ( obj.attachEvent ) {

        obj['e'+type+fn] = fn;
        obj[type+fn] = function(){obj['e'+type+fn]( window.event );};
        obj.attachEvent( 'on'+type, obj[type+fn] );
    } else {
        obj.addEventListener( type, fn, false );
    }
}
function getScrollY() {
    var  scrOfY = 0;
    if( typeof( window.pageYOffset ) == 'number' ) {
        //Netscape compliant
        scrOfY = window.pageYOffset;

    } else if( document.body && document.body.scrollTop )  {
        //DOM compliant
        scrOfY = document.body.scrollTop;
    } 
    return scrOfY;
}
</script>
<style>
#mydiv {
    height:100px;
    width:100%;
}
#fdiv {
    height:100px;
    width:100%;
}
</style>
</head>

<body>

<!-- HTML for example event goes here -->

<div id="fdiv" style="background-color:red;position:fix">
</div>
<div id="mydiv" style="background-color:yellow">
</div>
<div id="fdiv" style="background-color:green">
</div>

<script>

// Script for example event goes here

addEvent(window, 'scroll', function(event) {

    var x = document.getElementById("mydiv");

    var y = getScrollY();      
    if (y >= 100) {
        x.style.position = "fixed"; 
        x.style.top= "0";
    } 
});

</script>
</body>
</html>
Naomi Fridman
  • 2,095
  • 2
  • 25
  • 36
4

Wont the below basic approach doesn't suffice your requirements?

HTML Code having a div

<div id="mydiv" onscroll='myMethod();'>


JS will have below code

function myMethod(){ alert(1); }
Satya
  • 429
  • 3
  • 8
2

If scroll event is not working for some reason then try wheel event instead:

document.addEventListener('wheel', (event) => {console.log('scrolled')});
GorvGoyl
  • 42,508
  • 29
  • 229
  • 225
  • 3
    `scroll` event somehow doesn't work if you hide the body level overflow and replace it with scroll connected to div. `wheel` event works in this situation. – JkAlombro Feb 09 '23 at 05:47
0

Is there a js listener for when a user scrolls in a certain textbox that can be used?

DOM L3 UI Events spec gave the initial definition but is considered obsolete.

To add a single handler you can do:

  let isTicking;
  const debounce = (callback, evt) => {
    if (isTicking) return;
    requestAnimationFrame(() => {
      callback(evt);
      isTicking = false;
    });
    isTicking = true;
  };
  const handleScroll = evt => console.log(evt, window.scrollX, window.scrollY);
  document.defaultView.onscroll = evt => debounce(handleScroll, evt);

For multiple handlers or, if preferable for style reasons, you may use addEventListener as opposed to assigning your handler to onscroll as shown above.

If using something like _.debounce from lodash you could probably get away with:

const handleScroll = evt => console.log(evt, window.scrollX, window.scrollY);
document.defaultView.onscroll = evt => _.debounce(() => handleScroll(evt));

Review browser compatibility and be sure to test on some actual devices before calling it done.

vhs
  • 9,316
  • 3
  • 66
  • 70
-1
let lastKnownScrollPosition = 0;
let ticking = false;

function doSomething(scrollPos) {
  // Do something with the scroll position
}

document.addEventListener('scroll', function(e) {
  lastKnownScrollPosition = window.scrollY;

  if (!ticking) {
    window.requestAnimationFrame(function() {
      doSomething(lastKnownScrollPosition);
      ticking = false;
    });

    ticking = true;
  }
});
Tithos
  • 1,191
  • 4
  • 17
  • 40