Reputation: 2123
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'
});
Upvotes: 1
Views: 6265
Reputation: 136708
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
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'
});
Upvotes: 4
Reputation: 31
$(window).scroll(function(){
if($(window).scrollTop() + $(window).height() >= $(document).height()) {
//Your Stuff
}
});
Upvotes: 0
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
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