62

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.

1

6 Answers 6

138

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 perform any 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;
};
Sign up to request clarification or add additional context in comments.

8 Comments

Just curious - and not sure if it matters - but should useCapture return false or true for the scroll event listener?
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.
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?
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).
Regarding the locked true/false check: won't the code only fire once (the first time the user scrolls) ??
|
6

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); }

2 Comments

In a narrow sence this is a function, not a method :-)
It depends if myMethod is global - window.myMethod ;).
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>

Comments

4

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

document.addEventListener('wheel', (event) => {console.log('scrolled')});

1 Comment

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.
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.

Comments

-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;
  }
});

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.