midstack
midstack

Reputation: 2123

How can I detect window scroll ended in javascript?

I want to add a class to selected element after scroll ended. How can I detect scroll ended in JS?

HTML

<ul class="list" id="wrapper">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>
    <li id="element">7</li>
    <li>8</li>
    <li>9</li>
    <li>10</li>
 </ul>

JS

const element = document.getElementById('element');
const y = element.getBoundingClientRect().top + window.scrollY;


window.scroll({
  top: y,
  behavior: 'smooth'
});

JSBIN EXAMPLE

Upvotes: 1

Views: 6265

Answers (5)

Kaiido
Kaiido

Reputation: 136708

2023 Update:

There is now a scrollend event you can listen to. This will fire when the scroll ends, be it from user scroll or from programmatic smooth scroll. The browser support isn't great yet, but it should be available everywhere soon enough.

if (!("onscrollend" in window)) {
  console.warn("Your browser doesn't support the onscrollend event");
}

const trigger = document.getElementById( 'trigger' );
const scroll_forcer = document.getElementById( 'scroll_forcer' );

let scrolling = false; // a simple flag letting us know if we're already scrolling
trigger.onclick = (evt) => startScroll();

function startScroll() {
  setTimeout(()=> {
    scroll_forcer.classList.add( "scrolling" )
    document.scrollingElement.scrollTo( { top: 1000, behavior: "smooth" } );
    document.addEventListener( "scrollend", (evt) => {
      scroll_forcer.classList.remove( "scrolling" );
    }, { once: true });
    }, 10);
};
#scroll_forcer {
  height: 5000vh;
  background-image: linear-gradient(to bottom, red, green);
  background-size: 100% 100px;
}
#scroll_forcer.scrolling {
  filter: grayscale(70%);
}
<button id="trigger">click to scroll</button>
<div id="scroll_forcer">
</div>

So for the time being, I may still need the previous answer:


There is natively no event to tell when a smooth-scroll ends.

There is also no standard behavior for how this smooth-scroll should occur, no defined duration, no defined timing function (Chrome uses an ease-in-out function while Firefox uses a linear one), and added to that, a smooth-scroll can be canceled in the middle by an other call to the scroll algorithm, or even have no effect at all...

So this detection is not that easy.

The best way I found for now is to start a requestAnimationFrame powered loop which will check every painting frame (right after the scroll operations), if our target is at the same position. As soon as it has been at the same position for more than two frames, we assume the scrolling ended. That's when we can check if it succeeded, simply by checking if we are at the expected position:

const trigger = document.getElementById( 'trigger' );
const scroll_forcer = document.getElementById( 'scroll_forcer' );

let scrolling = false; // a simple flag letting us know if we're already scrolling
trigger.onclick = (evt) => startScroll();

function startScroll() {
  setTimeout(()=> {
    scroll_forcer.classList.add( "scrolling" )
    smoothScrollTo( { top: 1000 } )
      .catch( (err) => {
        /*
          here you can handle when the smooth-scroll
          gets disabled by an other scrolling
        */
        console.error( 'failed to scroll to target' );
      } )
      // all done, lower the flag
      .then( () => scroll_forcer.classList.remove( "scrolling" ) );
    }, 10);
};


/* 
 *
 * Promised based window.scrollTo( { behavior: 'smooth' } )
 * @param { Element } elem
 **  ::An Element on which we'll call scrollIntoView
 * @param { object } [options]
 **  ::An optional scrollToOptions dictionary
 * @return { Promise } (void)
 **  ::Resolves when the scrolling ends
 *
 */
function smoothScrollTo( options ) {
  return new Promise( (resolve, reject) => {
    const elem = document.scrollingElement;
    let same = 0; // a counter

    // last known scroll positions
    let lastPos_top = elem.scrollTop;
    let lastPos_left = elem.scrollLeft;

    // pass the user defined options along with our default
    const scrollOptions = Object.assign( {
        behavior: 'smooth',
        top: lastPos_top,
        left: lastPos_left
      }, options );

    // expected final position
    const maxScroll_top = elem.scrollHeight - elem.clientHeight;
    const maxScroll_left = elem.scrollWidth - elem.clientWidth;
    const targetPos_top = Math.max( 0, Math.min(  maxScroll_top, scrollOptions.top ) );
    const targetPos_left = Math.max( 0, Math.min( maxScroll_left, scrollOptions.left ) );

    // let's begin
    window.scrollTo( scrollOptions );
    requestAnimationFrame( check );
    
    // this function will be called every painting frame
    // for the duration of the smooth scroll operation
    function check() {
      // check our current position
      const newPos_top = elem.scrollTop;
      const newPos_left = elem.scrollLeft;
      // we add a 1px margin to be safe
      // (can happen with floating values + when reaching one end)
      const at_destination = Math.abs( newPos_top - targetPos_top) <= 1 &&
        Math.abs( newPos_left - targetPos_left ) <= 1;
      // same as previous
      if( newPos_top === lastPos_top &&
        newPos_left === lastPos_left ) {
        if( same ++ > 2 ) { // if it's more than two frames
          if( at_destination ) {
            return resolve();
          }
          return reject();
        }
      }
      else {
        same = 0; // reset our counter
        // remember our current position
        lastPos_top = newPos_top;
        lastPos_left = newPos_left;
      }
      // check again next painting frame
      requestAnimationFrame( check );
    }
  });
}
#scroll_forcer {
  height: 5000vh;
  background-image: linear-gradient(to bottom, red, green);
  background-size: 100% 100px;
}
#scroll_forcer.scrolling {
  filter: grayscale(70%);
}
.as-console-wrapper {
  max-height: calc( 50vh - 30px ) !important;
}
<button id="trigger">click to scroll</button>
<div id="scroll_forcer">
</div>

Upvotes: 8

Fabrizio Calderan
Fabrizio Calderan

Reputation: 123387

You could use requestAnimationFrame to detect when the scrollTop is greater than y

requestAnimationFrame is way better than setting both an interval and an event listener on scroll, for a matter of performance.

const element = document.getElementById('element');
const y = element.getBoundingClientRect().top + window.scrollY;


function checkScrollEnd() {
  if ((window.scrollY || document.body.scrollTop || document.documentElement.scrollTop) < y) {
    window.requestAnimationFrame(checkScrollEnd);
  }
  else {
    alert('end of scroll')   
  }
}

window.requestAnimationFrame(checkScrollEnd);


window.scroll({
  top: y,
  behavior: 'smooth'  
});

Example fiddle

Upvotes: 4

Senthil
Senthil

Reputation: 31

$(window).scroll(function(){
    if($(window).scrollTop() + $(window).height() >= $(document).height()) {
    //Your Stuff
}
});

Upvotes: 0

Razi
Razi

Reputation: 93

Solution by LINK

const onScrollStop = (callback) => {

        // Make sure a valid callback was provided
        if (!callback || typeof callback !== 'function') return;

        // Setup scrolling variable
        let isScrolling;

        // Listen for scroll events
        window.addEventListener('scroll', (event) => {

            // Clear our timeout throughout the scroll
            window.clearTimeout(isScrolling);

            // Set a timeout to run after scrolling ends
            isScrolling = setTimeout(() => {

                // Run the callback
                callback();

            }, 66);

        }, false);

    };

    onScrollStop(() => {
        console.log('Scrolling has stopped.');
    });

Upvotes: 1

Joe
Joe

Reputation: 50

Hope this will help you

<script type="text/javascript">
        jQuery(
          function ($) {
              $(window).on("scroll", function () {
                  var scrollHeight = $(document).height();
                  var scrollPosition = $(window).height() + $(window).scrollTop();
                  if (scrollHeight - scrollPosition <= 180) {
                      // when scroll to bottom of the page

                         // your function to call

                  }
              });
          }
        );
</script>

Upvotes: 0

Related Questions