I have the following code:
if(changedStatus()){
var mAns = confirm("You have changed your status. This will alter your estate plan and may cause you to lose some data. Are you sure that you want to do this?");
if(!mAns) return false;
}
This works great with the default JS confirm pop-up, but I would like to implement the same action with HTML5 buttons instead:
<div id="cres_statusmssg">
<p>Changing your status will alter your estate plan and may cause you to lose some data. Do you want to proceed?</p>
<button id="cres_yes">Yes</button>
<button id="cres_cancel">Cancel</button>
I have applied click events to both buttons, but I can't get them to work. Any ideas?
You'll need to understand how to write for asynchrony, since there is no other way to achieve what you want to do. The built-in confirm (and prompt, alert etc) effectively pause JS execution until the user has interacted with the pop-up
Writing nicer pop-ups, you don't have that luxury. You'll need to use callbacks to achieve a working solution
I'd recommend using Promises - which are just glorified callbacks anyway, with the added advantage of being able to be used with async/await - this has the advantage of making the code look more like the code you're used to if you haven't done much asynchronous coding
function statusmssgDialog() {
const dlg = document.getElementById('cres_statusmssg');
dlg.showModal();
return new Promise((resolve, reject) => {
const clk = function(e) {
resolve(this.dataset.value === 'true');
dlg.close();
};
dlg.querySelectorAll('button').forEach(btn =>
btn.addEventListener('click', clk, { once: true})
);
});
}
// usage - it's easier if the function where you
// call statusmssgDialog is `async`
// but you can also use Promise.then/.catch of course
//
async function fn() {
if( true /*changedStatus()*/){
const mAns = await statusmssgDialog();
console.log(`user action is ${mAns}`);
if(!mAns) return mAns;
}
console.log('doing more things here when yes is clicked or chengedStatus is false?');
}
// asynchrony is like a virus,
// anything that calls an asynchronous function and needs the result
// needs to be written to account for asynchrony
// here, I use .then/.catch instead of async/await
// because await isn't an option at the top-level of code
// unless it's the top-level of a module script in modern browsers
fn().then(r => {
if (r === false) {
console.log('cancel was clicked');
return;
}
console.log('yes was clicked');
});
.modal-dialog {
border-radius: 9px;
box-shadow: 0 0 1em rgb(127 127 127 / 0.8);
width: 30rem;
max-width: 90%;
}
.modal-dialog::backdrop {
background: rgb(0 0 0 / 0.4);
backdrop-filter: blur(1px);
}
body {
background-color: hsl(240 100% 95%);
}
<dialog id="cres_statusmssg" class="modal-dialog">
<div>
<p>Changing your status will alter your estate plan and may cause you to lose some data. Do you want to proceed?</p>
<button id="cres_yes" data-value="true">Yes</button>
<button id="cres_cancel" data-value="false">Cancel</button>
</div>
</dialog>
<div>
<p>Dummy page text</p>
<div>
In the above, the dialog promise always resolves, but there is an alternative, to reject if cancel is pressed
function statusmssgDialog() {
const dlg = document.getElementById('cres_statusmssg');
dlg.showModal();
return new Promise((resolve, reject) => {
const clk = function(e) {
dlg.close();
if (this.dataset.value !== 'true') {
return reject('cancel pressed');
}
resolve('yes pressed');
};
dlg.querySelectorAll('button').forEach(btn =>
btn.addEventListener('click', clk, { once: true})
);
});
}
async function fn() {
if( true /*changedStatus()*/) {
try {
const mAns = await statusmssgDialog();
console.log(`yes pressed`);
} catch(e) {
console.log('cancel was pressed');
throw e; // pass the cancel "error" to the caller
}
}
console.log('doing more things here when yes is clicked or chengedStatus is false?');
}
fn().then(r => {
console.log('yes was clicked');
}).catch(e => {
console.log(`user action was ${e}`);
});
.modal-dialog {
border-radius: 9px;
box-shadow: 0 0 1em rgb(127 127 127 / 0.8);
width: 30rem;
max-width: 90%;
}
.modal-dialog::backdrop {
background: rgb(0 0 0 / 0.4);
backdrop-filter: blur(1px);
}
body {
background-color: hsl(240 100% 95%);
}
<dialog id="cres_statusmssg" class="modal-dialog">
<div>
<p>Changing your status will alter your estate plan and may cause you to lose some data. Do you want to proceed?</p>
<button id="cres_yes" data-value="true">Yes</button>
<button id="cres_cancel" data-value="false">Cancel</button>
</div>
</dialog>
<div>
<p>Dummy page text</p>
<div>
Try to use Bootstrap Modal
Using Twitter bootstrap modal instead of confirm dialog
Or jquery ui
How to implement "confirmation" dialog in Jquery UI dialog?
Related
It is possible to stop an insertBefore, inside an addEventListener, to add a smooth css, so that the movement produced by the insertion of the div is not abrupt for the user?
I have read many questions, i have tried using settimeout in various ways, without success:
const gallery = document.getElementById('gallery');
const frames = gallery.querySelectorAll('.frame');
for (var i = 0; i < frames.length; ++i) {
frames[i].addEventListener('click', function() {
if (this.className == "frame b") {
//setTimeout( function(){
gallery.insertBefore(this, this.previousElementSibling);
//}, 1000 );
} else {
//setTimeout( function(){
gallery.insertBefore(this, this.previousElementSibling);
//}, 1000 );
};
});
};
.frame {
width: 200px;
height: 100px;
font: bold 400% sans-serif;
color: white;
float: left;
}
.frame.a {
background-color: brown;
}
.frame.b {
background-color: purple;
}
<div id="gallery">
<div class="frame a">A</div>
<div class="frame b">B</div>
</div>
this refers to a different context inside your setTimeout callback; it doesn't refer to the element for which the event is dispatched anymore.
There are a few ways you could do this, here are 3:
Use an arrow function, where this doesn't get bound to a new context:
setTimeout( () => {
gallery.insertBefore(this , this.previousElementSibling);
}, 1000 );
Store a reference to this for use inside the callback:
const _self = this;
setTimeout( function(){
gallery.insertBefore(_self , _self.previousElementSibling);
}, 1000 );
Manually bind the current context to the callback function:
setTimeout( function(){
gallery.insertBefore(this, this.previousElementSibling);
}.bind(this), 1000 );
Another angle I prefer for this is use this wait fn, tidy & concise.
wait fn src
// at the top of the file, or 1000 here and leave out 1000 in the call
const wait = (delay = 300) => new Promise((resolve) => setTimeout(resolve, delay));
// inside
frames[i].addEventListener('click', async () => {
await wait(1000)
if (this.className == "frame b") {
gallery.insertBefore(this, this.previousElementSibling);
} else {
gallery.insertBefore(this, this.previousElementSibling);
};
});
Only trouble is sometimes forgetting the await keyword, hopefully you have an IDE that tells You when you don't need it, like prettier... Won't tell You when you've forgotten though since there are other valid ways to use it. Outer for loop is separate of this question since it's just assigning refs for later.
There is also now
import {setTimeout} from "timers/promises";
await setTimeout(1000);
From Node 16, you don't need the wait fn.
I am using Javascript method Element.scrollIntoView()
https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
Is there any way I can get to know when the scroll is over. Say there was an animation, or I have set {behavior: smooth}.
I am assuming scrolling is async and want to know if there is any callback like mechanism to it.
There is no scrollEnd event, but you can listen for the scroll event and check if it is still scrolling the window:
var scrollTimeout;
addEventListener('scroll', function(e) {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(function() {
console.log('Scroll ended');
}, 100);
});
2022 Update:
The CSS specs recently included the overscroll and scrollend proposal, this proposal adds a few CSS overscroll attributes, and more importantly to us, a scrollend event.
Browsers are still working on implementing it. (It's already available in Chromium under the Web Platforms Experiments flag.)
We can feature-detect it by simply looking for
if (window.onscrollend !== undefined) {
// we have a scrollend event
}
While waiting for implementations everywhere, the remaining of this answer is still useful if you want to build a polyfill:
For this "smooth" behavior, all the specs say[said] is
When a user agent is to perform a smooth scroll of a scrolling box box to position, it must update the scroll position of box in a user-agent-defined fashion over a user-agent-defined amount of time.
(emphasis mine)
So not only is there no single event that will fire once it's completed, but we can't even assume any stabilized behavior between different browsers.
And indeed, current Firefox and Chrome already differ in their behavior:
Firefox seems to have a fixed duration set, and whatever the distance to scroll is, it will do it in this fixed duration ( ~500ms )
Chrome on the other hand will use a speed, that is, the duration of the operation will vary based on the distance to scroll, with an hard-limit of 3s.
So this already disqualifies all the timeout based solutions for this problem.
Now, one of the answers here has proposed to use an IntersectionObserver, which is not a too bad solution, but which is not too portable, and doesn't take the inline and block options into account.
So the best might actually be to check regularly if we did stop scrolling. To do this in a non-invasive way, we can start an requestAnimationFrame powered loop, so that our checks are performed only once per frame.
Here one such implementation, which will return a Promise that will get resolved once the scroll operation has finished.
Note: This code misses a way to check if the operation succeeded, since if an other scroll operation happens on the page, all current ones are cancelled, but I'll leave this as an exercise for the reader.
const buttons = [ ...document.querySelectorAll( 'button' ) ];
document.addEventListener( 'click', ({ target }) => {
// handle delegated event
target = target.closest('button');
if( !target ) { return; }
// find where to go next
const next_index = (buttons.indexOf(target) + 1) % buttons.length;
const next_btn = buttons[next_index];
const block_type = target.dataset.block;
// make it red
document.body.classList.add( 'scrolling' );
smoothScroll( next_btn, { block: block_type })
.then( () => {
// remove the red
document.body.classList.remove( 'scrolling' );
} )
});
/*
*
* Promised based scrollIntoView( { behavior: 'smooth' } )
* #param { Element } elem
** ::An Element on which we'll call scrollIntoView
* #param { object } [options]
** ::An optional scrollIntoViewOptions dictionary
* #return { Promise } (void)
** ::Resolves when the scrolling ends
*
*/
function smoothScroll( elem, options ) {
return new Promise( (resolve) => {
if( !( elem instanceof Element ) ) {
throw new TypeError( 'Argument 1 must be an Element' );
}
let same = 0; // a counter
let lastPos = null; // last known Y position
// pass the user defined options along with our default
const scrollOptions = Object.assign( { behavior: 'smooth' }, options );
// let's begin
elem.scrollIntoView( 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 = elem.getBoundingClientRect().top;
if( newPos === lastPos ) { // same as previous
if(same ++ > 2) { // if it's more than two frames
/* #todo: verify it succeeded
* if(isAtCorrectPosition(elem, options) {
* resolve();
* } else {
* reject();
* }
* return;
*/
return resolve(); // we've come to an halt
}
}
else {
same = 0; // reset our counter
lastPos = newPos; // remember our current position
}
// check again next painting frame
requestAnimationFrame(check);
}
});
}
p {
height: 400vh;
width: 5px;
background: repeat 0 0 / 5px 10px
linear-gradient(to bottom, black 50%, white 50%);
}
body.scrolling {
background: red;
}
<button data-block="center">scroll to next button <code>block:center</code></button>
<p></p>
<button data-block="start">scroll to next button <code>block:start</code></button>
<p></p>
<button data-block="nearest">scroll to next button <code>block:nearest</code></button>
<p></p>
<button>scroll to top</button>
You can use IntersectionObserver, check if element .isIntersecting at IntersectionObserver callback function
const element = document.getElementById("box");
const intersectionObserver = new IntersectionObserver((entries) => {
let [entry] = entries;
if (entry.isIntersecting) {
setTimeout(() => alert(`${entry.target.id} is visible`), 100)
}
});
// start observing
intersectionObserver.observe(element);
element.scrollIntoView({behavior: "smooth"});
body {
height: calc(100vh * 2);
}
#box {
position: relative;
top:500px;
}
<div id="box">
box
</div>
I stumbled across this question as I wanted to focus a particular input after the scrolling is done (so that I keep the smooth scrolling).
If you have the same usecase as me, you don't actually need to wait for the scroll to be finished to focus your input, you can simply disable the scrolling of focus.
Here is how it's done:
window.scrollTo({ top: 0, behavior: "smooth" });
myInput.focus({ preventScroll: true });
cf: https://github.com/w3c/csswg-drafts/issues/3744#issuecomment-685683932
Btw this particular issue (of waiting for scroll to finish before executing an action) is discussed in CSSWG GitHub here: https://github.com/w3c/csswg-drafts/issues/3744
Solution that work for me with rxjs
lang: Typescript
scrollToElementRef(
element: HTMLElement,
options?: ScrollIntoViewOptions,
emitFinish = false,
): void | Promise<boolean> {
element.scrollIntoView(options);
if (emitFinish) {
return fromEvent(window, 'scroll')
.pipe(debounceTime(100), first(), mapTo(true)).toPromise();
}
}
Usage:
const element = document.getElementById('ELEM_ID');
scrollToElementRef(elment, {behavior: 'smooth'}, true).then(() => {
// scroll finished do something
})
These answers above leave the event handler in place even after the scrolling is done (so that if the user scrolls, their method keeps getting called). They also don't notify you if there's no scrolling required. Here's a slightly better answer:
$("#mybtn").click(function() {
$('html, body').animate({
scrollTop: $("div").offset().top
}, 2000);
$("div").html("Scrolling...");
callWhenScrollCompleted(() => {
$("div").html("Scrolling is completed!");
});
});
// Wait for scrolling to stop.
function callWhenScrollCompleted(callback, checkTimeout = 200, parentElement = $(window)) {
const scrollTimeoutFunction = () => {
// Scrolling is complete
parentElement.off("scroll");
callback();
};
let scrollTimeout = setTimeout(scrollTimeoutFunction, checkTimeout);
parentElement.on("scroll", () => {
clearTimeout(scrollTimeout);
scrollTimeout = setTimeout(scrollTimeoutFunction, checkTimeout);
});
}
body { height: 2000px; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="mybtn">Scroll to Text</button>
<br><br><br><br><br><br><br><br>
<div>example text</div>
i'm not an expert in javascript but i made this with jQuery. i hope it helps
$("#mybtn").click(function() {
$('html, body').animate({
scrollTop: $("div").offset().top
}, 2000);
});
$( window ).scroll(function() {
$("div").html("scrolling");
if($(window).scrollTop() == $("div").offset().top) {
$("div").html("Ended");
}
})
body { height: 2000px; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="mybtn">Scroll to Text</button>
<br><br><br><br><br><br><br><br>
<div>example text</div>
I recently needed callback method of element.scrollIntoView(). So tried to use the Krzysztof Podlaski's answer.
But I could not use it as is. I modified a little.
import { fromEvent, lastValueFrom } from 'rxjs';
import { debounceTime, first, mapTo } from 'rxjs/operators';
/**
* This function allows to get a callback for the scrolling end
*/
const scrollToElementRef = (parentEle, childEle, options) => {
// If parentEle.scrollTop is 0, the parentEle element does not emit 'scroll' event. So below is needed.
if (parentEle.scrollTop === 0) return Promise.resolve(1);
childEle.scrollIntoView(options);
return lastValueFrom(
fromEvent(parentEle, 'scroll').pipe(
debounceTime(100),
first(),
mapTo(true)
)
);
};
How to use
scrollToElementRef(
scrollableContainerEle,
childrenEle,
{
behavior: 'smooth',
block: 'end',
inline: 'nearest',
}
).then(() => {
// Do whatever you want ;)
});
I'm trying to execute some code immediately after a CSS transition finishes. The problem is that in certain cases (not determined by me), the transition does not have to occur. How do I know when to expect a transition? I know about the transitionrun and transitionstart events and I tried to use them, but they don't do the job:
function animateIn (elem, action) {
var expecting = false
setTimeout(() => {
console.log('timeout', expecting)
}, 0)
window.requestAnimationFrame(() => {
console.log('raf', expecting)
})
elem.addEventListener('transitionrun', () => {
expecting = true
console.log('transitionrun')
})
elem.addEventListener('transitionstart', () => {
expecting = true
console.log('transitionstart')
})
elem.addEventListener('transitionend', () => {
console.log('transitionend')
})
action()
}
var elem = document.getElementById('main')
elem.addEventListener('click', () => {
animateIn(elem, () => {
elem.classList.remove('is-enter')
})
})
#main {
background: red;
width: 80px;
height: 80px;
transition: background 0.5s ease;
}
#main.is-enter {
background: blue;
}
<div id="main" class="is-enter"></div>
As you can see, I want to use the expecting variable to check whether a transition will start after whatever happens in the action function. And that's what I basically want - to check if a certain action (the removal of a class in this case) causes a transition to run.
My idea was to use requestAnimationFrame or setTimeout to wait for a little bit and if transitionrun or transitionstart has fired in the meantime - then a transition is expected, otherwise - it's not.
The problem with that is the fact that both requestAnimationFrame and setTimeout run before transitionrun and transitionstart had the chance to fire (at least in Chrome). So it appears that the transition takes longer to start than one render loop? Why is that?
One workaround is to use an arbitrary timeout that gives the transition enough time to eventually start:
function checkTransition (elem, action) {
return new Promise ((resolve, reject) => {
var willTransition = false
var startHandler = function () {
willTransition = true
elem.addEventListener('transitionend', function endHandler () {
elem.removeEventListener('transitionend', endHandler)
resolve(true)
})
}
elem.addEventListener('transitionstart', startHandler)
setTimeout(() => {
elem.removeEventListener('transitionstart', startHandler)
if (!willTransition) {
resolve(false)
}
}, 100)
action()
})
}
var elem = document.getElementById('main')
elem.addEventListener('click', () => {
checkTransition(elem, () => {
elem.classList.remove('is-enter')
}).then(status => {
console.log('had transition:', status)
})
})
#main {
background: red;
width: 80px;
height: 80px;
transition: background 0.5s ease;
}
#main.is-enter {
background: blue;
}
<div id="main" class="is-enter"></div>
When you click the box for the first time, the Promise waits for the transition to finish and resolves with true to signal that a transition has happened. If you click it more times after that, it resolves with false because no transition occurred. That's exactly what I need.
The problem is setTimeout. That 100ms wait is completely arbitrary and very error-prone, I suppose. Is there a better way to capture this? I need some sort of way to fire code immediately after a transitionstart might occur. I was hoping that requestAnimationFrame would do this but it doesn't. Why?
It appears to work in Chrome (latest), Firefox (latest) and IE11 with a double requestAnimationFrame, even for delayed transitions:
function checkTransition (elem, action) {
return new Promise ((resolve, reject) => {
var willTransition = false
var startHandler = function () {
willTransition = true
elem.addEventListener('transitionend', function endHandler () {
elem.removeEventListener('transitionend', endHandler)
resolve(true)
})
}
elem.addEventListener('transitionrun', startHandler)
elem.addEventListener('transitionstart', startHandler)
window.requestAnimationFrame(() => {
window.requestAnimationFrame(() => {
elem.removeEventListener('transitionrun', startHandler)
elem.removeEventListener('transitionstart', startHandler)
if (!willTransition) {
resolve(false)
}
})
})
action()
})
}
function bind (elem) {
elem.addEventListener('click', () => {
checkTransition(elem, () => {
elem.classList.remove('is-enter')
}).then(status => {
console.log('had transition:', status)
})
})
}
bind(document.getElementById('main'))
bind(document.getElementById('delayed'))
.target {
display: inline-block;
width: 80px;
height: 80px;
margin-right: 5px;
background: red;
}
.target.is-enter {
background: blue;
}
#main {
transition: background 0.5s ease;
}
#delayed {
transition: background 0.5s ease 0.4s;
}
<div id="main" class="target is-enter"></div>
<div id="delayed" class="target is-enter"></div>
I think it's far better than an arbitrary timeout but I still have doubts whether it's reliable. To answer that, I think we need to answer first why one requestAnimationFrame call is not enough in Chrome.
Edit: This trick doesn't seem to work in Chrome on mobile.
I'm currently working through the Single Page Web Applications by Mikowski and Powell. After working through the simple tutorial in Chapter 1, I am confused on why it is necessary to return true and return false in the toggleSlider(), onClickSlider(), and initModule() functions.
What is the added benefit of doing so? When I ran the below code without the return true and return false, it worked exactly the same as with the return statements.
What is an appropriate situation for which having these return statements is actually beneficial and necessary?
var spa = (function($) {
var configMap = {
extended_height: 434,
extended_title: 'Click to retract',
retracted_height: 16,
retracted_title: 'Click to extend',
template_html: '<div class="spa-slider"><\/div>'
},
$chatSlider,
toggleSlider, onClickSlider, initModule;
toggleSlider = function() {
var slider_height = $chatSlider.height();
if (slider_height === configMap.retracted_height) {
$chatSlider
.animate({
height: configMap.extended_height
})
.attr('title', configMap.extended_title);
return true;
} else if (slider_height === configMap.extended_height) {
$chatSlider
.animate({
height: configMap.retracted_height
})
.attr('title', configMap.retracted_title);
return true;
}
console.log("Nothing to extend or retract. No events fired.");
return false;
};
onClickSlider = function(event) {
console.log("Calling onClickSlider click event");
toggleSlider();
return false;
};
initModule = function($container) {
$container.html(configMap.template_html);
$chatSlider = $container.find('.spa-slider');
$chatSlider
.attr('title', configMap.retracted_title)
.click(onClickSlider);
return true;
};
return {
initModule: initModule
};
}(jQuery));
jQuery(document).ready(
function() {
spa.initModule(jQuery('#spa'));
}
);
body {
width: 100%;
height: 100%;
overflow: hidden;
background-color: #777;
}
#spa {
position: absolute;
top: 8px;
left: 8px;
bottom: 8px;
right: 8px;
border-radius: 8px 8px 0 8px;
background-color: #fff;
}
.spa-slider {
position: absolute;
bottom: 0;
right: 2px;
width: 300px;
height: 16px;
cursor: pointer;
border-radius: 8px 0 0 0;
background-color: #f00;
}
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="spa">
<div class="spa-slider"></div>
</div>
</script>
Often, in event handlers, returning false is a way to tell the event to not actually fire. So, for example, in an onsubmit case, this would mean that the form is not submitted.
In your example return true; will make the animation occur, while return false; won't.
Alternatively, you can do e.preventdefault() instead of return false;.
Return values like this in Javascript functions are often used to indicate success or failure. You can create a simple flow-control structure by doing something like:
var doSomething = function() {
if (error) {
return false;
} else {
return true;
}
};
if (doSomething()) {
doSomethingElse();
} else {
console.log("There was an error!");
}
That said, it's rarely a good idea to use this for anything other than a quick demo. return false is notorious for producing unexpected results when used purely in this manner - there's almost always a better option to achieve your goal (unless your goal is returning a boolean value!). If you just need to escape an active function, you can just use return;.
In your specific code, toggleSlider() appears to be returning these values to indicate activity, while onClickSlider() is using return false in place of e.preventDefault(), as Nicholas mentioned in their answer. You can read more about why this is often a bad idea here: http://fuelyourcoding.com/jquery-events-stop-misusing-return-false/
It seems to me you are looking for a hard-set rule where there really isn't one.
You ask "Why" this rule exists when it really doesn't.
You are still reading chapter 1 of Mikowski and Powell's Single Page Web Applications and I am very happy they get you to think and to notice your function's returned value.
There are many possible returned values - true and falseare just two very common and very important options.
In more advanced functions you might find yourself returning self or an object of any number of different return value's - all according to the application's architecture and what you need.
For instance, in Object Oriented Programming, it is becoming more and more common to return self for simple functions. This allows you to chain different functions in one line... but what does Object Oriented Programming have to do with chapter 1?
I recommend you allow yourself more time and experience. For now, I would suggest that you trust the process. Mikowski and Powell are doing their best to feed you a lot of knowledge... but they have to divide it into smaller bites and pieces.
Good Luck!
How can I trigger a function when the browser window stops scrolling? Either by mouse wheel, click, space bar or arrow key? Is there any event for such action? I have tried to search online but couldn't get any solution. I'm fine with a jQuery solution.
There's no "event" but you could make your own, like this:
$(function() {
var timer;
$(window).scroll(function() {
clearTimeout(timer);
timer = setTimeout(function() {
$(window).trigger("scrollStop");
}, 250);
});
});
Then you could bind to it, like this:
$(window).bind("scrollStop", function() {
alert("No one has scrolled me in 250ms, where's the love?");
});
This creates an event, there's no "stop" for this, but you can define your own... in this case "stop" is defined as "hasn't scrolled in 250ms", you can adjust the timer to your liking, but that's the idea.
Also, if you're just doing one thing there's no need for an event, just put your code where I'm calling $(window).trigger("scrollStop") and it'll run n milliseconds after the scroll stops.
The Non-jQuery Javascript version of the chosen answer:
var elem = document.getElementById('test');
(() => {
var onScrollStop = (evt) => {
// you have scroll event as evt (for any additional info)
var scrollStopEvt = new CustomEvent('scrolling-stopped', {detail: 'foobar stopped :)'});
elem.dispatchEvent(scrollStopEvt);
}
var scrollStopLag = 300 // the duration to wait before onScrollStop is triggerred.
var timerID = 0;
const handleScroll = (evt) => {
clearInterval(timerID);
timerID = setTimeout(
() => onScrollStop(evt),
scrollStopLag
)
}
elem.addEventListener('scroll', handleScroll);
})()
elem.addEventListener(
'scrolling-stopped',
(evt) => console.log(evt.detail)
)
#test {
height: 300px;
width: 300px;
overflow: auto;
}
#test #test-inner {
height: 3000px;
background: linear-gradient(lightseagreen 0%, lightyellow 40%, lightcoral 100%);
}
<h4>Scroll inside the green box below:</h4>
<div id="test">
<div id="test-inner"></div>
</div>
Good Luck...
P.S. I am a BIG fan of jQuery :)