wheel event PreventDefault does not cancel wheel event - javascript

I would like to get one event only per scroll event
I try this code but it produces "wheel" as many times the wheel event is triggered.
Any help? Thank you
window.addEventListener("wheel",
(e)=> {
console.log("wheel");
e.preventDefault();
},
{passive:false}
);
Use case (edit)
I want to allow scrolling from page to page only - with an animation while scrolling. As soon I detect the onwheel event, I would like to stop it before the animation finishes, otherwise the previous onwheel continues to fire and it is seen as new event, so going to the next of the targeted page
My conclusion :
It is not possible to cancel wheel events. In order to identify a new user wheel action while wheeling events (from a former user action) are on going, we need to calculate the speed/acceleration of such events

This is fairly simple problem, store anywhere the last direction and coditionally execute your code:
direction = '';
window.addEventListener('wheel', (e) => {
if (e.deltaY < 0) {
//scroll wheel up
if(direction !== 'up'){
console.log("up");
direction = 'up';
}
}
if (e.deltaY > 0) {
//scroll wheel down
if(direction !== 'down'){
console.log("down");
direction = 'down';
}
}
});
Anyway, the UX context should be defined.
May be that throttling or debouncing your function will give better results in some scenarios.
Throttling
Throttling enforces a maximum number of times a function can be called
over time. As in "execute this function at most once every 100
milliseconds."
Debouncing
Debouncing enforces that a function not be called again until a
certain amount of time has passed without it being called. As in
"execute this function only if 100 milliseconds have passed without it
being called.
In your case, maybe debouncing is the best option.
Temporary lock the browser scroll
$('#test').on('mousewheel DOMMouseScroll wheel', function(e) {
e.preventDefault();
e.stopPropagation();
return false;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="test">
<h1>1</h1>
<h1>2</h1>
<h1>3</h1>
<h1>4</h1>
<h1>5</h1>
<h1>6</h1>
<h1>7</h1>
<h1>8</h1>
<h1>9</h1>
<h1>10</h1>
</div>

Event.preventDefault() tells the browser not to do the default predefined action for that event, such as navigating to a page or submitting the enclosing form, etc. It does not necessarily prevent events from firing.
Also, there is a difference between the wheel event and the scroll event. The wheel event is fired when the user rotates a wheel button, and the scroll event is fired when the target's scrollTop or scrollLeft property is changed due to the scroll position being changed.
When the user rotates the wheel button, the wheel event is fired before any scroll events that could be fired. However, the wheel event might not result in any scroll event simply because the pointer is not hovering on any element or the element is not scrollable at the moment.
To aggregate quickly repeated function calls to the event handler, you can debounce the event handler function. The idea is to wait a certain amount before committing to the action. When a function is debounced, it becomes a new function, when called, sets off a timer that calls the wrapped function inside. The timer is reset and restarted when debounced function is called again. Look at the example diagram below.
© Ilya Kantor(https://github.com/javascript-tutorial/en.javascript.info, licensed under CC-BY-NC)
The function f is a debounced function with a 1000ms timeout duration and is called at time instants 0, 200ms, and 500ms with arguments a, b, and c, respectively. Because f is debounced, calls f(a) and f(b) were "not committed/ignored" because there was another call to f within a 1000ms duration. Still, call f(c) was "committed/accepted" at the time instant 1500ms because no further call followed within 1000ms.
To implement this, you can use the setTimeout and clearTimeout functions. The setTimeout function accepts an action(code or function to execute) and delay in milliseconds, then returns a timer ID in integer. The given action will be executed when the timer expires without being canceled.
const timerId = setTimeout(action, delay)
The clearTimeout function could then be used to destroy the timer with a given ID.
clearTimeout(timerId)
Following simple debounce implementation could be used:
// Default timeout is set to 1000ms
function debounce(func, timeout = 1000) {
// A slot to save timer id for current debounced function
let timer
// Return a function that conditionally calls the original function
return (...args) => {
// Immediately cancel the timer when called
clearTimeout(timer)
// Start another timer that will call the original function
// unless canceled by following call
timer = setTimeout(() => {
// Pass all arguments and `this` value
func.apply(this, args)
}, timeout)
}
}
Read more: Default parameters, Rest parameters, Function.apply(), this keyword
To use is quite simple:
eventTarget.addEventListener('wheel', debounce((e) => {
console.log('wheel', e)
}))
This will limit console.log calls to whenever a wheel event has not been fired in a second.
Live example:
function debounce(f, d = 99, t) {
return (...a) => {
clearTimeout(t)
t = setTimeout(() => {
f.apply(this, a)
}, d)
}
}
document.addEventListener('wheel', debounce((_) => {
console.log('wheel')
}))
A more modern approach uses Promise on top of this idea.

You almost had it But you need to wrap your code in a function.
I added some extra little bits so you can differentiate up and down :)
//scroll wheel manipulation
window.addEventListener('wheel', function (e) {
//TODO add delay
if (e.deltaY < 0) {
//scroll wheel up
console.log("up");
}
if (e.deltaY > 0) {
//scroll wheel down
console.log("down");
}
});
How it works?
(e) = This is just the event, the function is triggered when ever you scroll up and down, but without the function event it just doesn't know what to do! Normally people put "event" but im lazy.
deltaY = This is a function of the wheel scroll it just makes sure you scrolling along the Y axis. Its a standard inbuilt function there is no external variables you need to add.
Extras
setTimeout
You could add this. In the if statements as #Lonnie Best suggested

You could set a minimum amount of time that must pass before you consider an additional scroll event as actionable.
For example, below, 3 seconds must pass between scroll events before console.log("wheel") is fired again:
function createScrollEventHandler(milliseconds)
{
let allowed = true;
return (event)=>
{
event.preventDefault();
if (allowed)
{
console.log("wheel");
allowed = false;
setTimeout(()=>
{
allowed = true;
},milliseconds);
}
}
}
let scrollEventHandler = createScrollEventHandler(3000); // 3 seconds
window.addEventListener("wheel",scrollEventHandler);

Related

This animation flickers at the end in IE 11

I have a banner that animates through a few background images. The animations are defined in CSS and triggered from JavaScript using the triggerAnimation() function in this fiddle and included below. It works by animating the next-banner div on top of the current-banner div, and then updating the current-banner background to the new image after the animation finishes and resets. The swap from the next-banner to the current-banner should be instantaneous, so that the user doesn't see it.
This works fine in both Firefox and Chrome, but in IE 11 it will occasionally flicker at the end of the animation. It resets back to the beginning state of the animation before the animationend callback makes the swap, causing a noticeable flicker from the new image to the old one, and then back. It happens unpredictably, and doesn't even seem to be consistent in how often it happens. Sometimes it will happen almost every time, while others I'll have to wait through 5 or more transitions before seeing it.
There is evidence that it's caused by the animationend event firing late, because if I set a timeout to 50ms after the animation should have ended, and cancel that timeout in the animationend callback, then my timeout callback only executes when the flicker occurs, and doesn't execute when it behaves normally.
triggerAnimation() code:
window.triggerAnimation = function(element, animation, callback) {
// omitted stuff to determine correct property and event names, which seems to work correctly
element.style[propName] = animation;
element.addEventListener(eventName, function(e) {
element.style[propName] = "";
element.removeEventListener(eventName, arguments.callee);
if (callback) callback(e);
}, false);
};
Is this a problem with my code or with IE, and how can I prevent this from happening?
The closest thing I've found to a solution is to set a timeout for just after when the animation should have finished, which "catches" the cases when IE doesn't fire the callback correctly. Then, the actual event handler cancels the timeout for the cases when the callback is fired correctly.
So, this is what I've ended up with:
window.triggerAnimation = function(element, animation, callback) {
// Omitted stuff to determine correct property and event names
element.style[propName] = animation;
// Extract the duration from the animation CSS and convert it to ms.
var duration = parseFloat(animation.match(/\d+\.\d+/g)[0], 10) * 1000;
var timeout = setTimeout(function() {
element.style[propName] = "";
element.removeEventListener(eventName, arguments.callee);
if (callback) callback(null);
}, duration + 10);
element.addEventListener(eventName, function(e) {
clearTimeout(timeout);
element.style[propName] = "";
element.removeEventListener(eventName, arguments.callee);
if (callback) callback(e);
}, false);
};
The only two downsides to this are: 1) it still will rarely flicker, but much less often and much less noticeably; and 2) any time that the timeout is invoked instead of the event handler, the event object (e) doesn't get passed to callback. In my case that's not a problem.
Add this code to CSS file:
html {
filter: expression(document.execCommand("BackgroundImageCache", false, true));
}

Stopping infinite loop on mouseleave or mouseout

I was experimenting on something which executes a function continuously when the mouse is down. I got it to loop infinitely which is exactly what I wanted but I cannot seem to stop that loop anymore.
I tried doing :-
$(document).on("mouseup mouseout",".someclass",function(){ loop(false) } );
which takes the false argument and should stop the loop. But the loop just goes on infinitely causing the page to crash. I want that infinite loop to stop whenever some event is called, be it mouseup, mouseout, mouseleave whichever.
My attempt so far:- http://jsfiddle.net/ZQTvN/
Do realize that this will crash your browser.
You can alter the code to use a flag and a setTimeout to achieve what you want:
A tight loop like this (busy-loop) will block any input from the browser so it is basically unable to parse the event queue, ie. browser gets locked.
By implementing a setTimout to do the loop you will allow the browser to queue other events as well, which you would need to detect when to stop it.
If you call your loop with an argument each time you will in fact only re-trigger it multiple times. When you call with false flag the flag will be local to that call and do nothing with the other loops that has started as they don't have access to this locallized flag.
So one approach is to put the flag outside(common flag, global scope):
MODIFIED FIDDLE HERE
Example:
var doLoop = false;
function loopy() {
if (doLoop === true) {
setTimeout(function () {
console.log(doLoop);
loopy();
}, 11);
}
};
$(document).on("mousedown", ".classy", function () {
doLoop = true;
loopy();
});
$(document).on("mouseup mouseout", ".classy", function () {
doLoop = false;
});
Now you can see the loop runs while holding the mouse down, and stops when released etc.

Differentiate click vs mousedown/mouseup

I've read several answers on stackoverflow pertaining to this situation, but none of the solutions are working.
I'm trying to do different things based upon whether a user clicks an element, or holds the mouse down on that element using jQuery.
Is it possible to accomplish this?
onMouseDown will trigger when either the left or right (or middle) is pressed. Similarly, onMouseUp will trigger when any button is released. onMouseDown will trigger even when the mouse is clicked on the object then moved off of it, while onMouseUp will trigger if you click and hold the button elsewhere, then release it above the object.
onClick will only trigger when the left mouse button is pressed and released on the same object. In case you care about order, if the same object has all 3 events set, it's onMouseDown, onMouseUp, then onClick. Each even should only trigger once though.
Details:
http://api.jquery.com/click/
http://api.jquery.com/mouseup/
http://api.jquery.com/mousedown/
Here's one approach
set a variable to true
make a function that will set it to false when called
have a timer ( setTimeout() ) start counting down on mousedown()
on mouseup, clear the timeout, and check if it the variable is true or false
if it is false, call the function you want to happen on click
In any case, set the variable back to true
This will do what you want.
Here's a jsfiddle showing how it might work: http://jsfiddle.net/zRr4s/3/
Here's a solution that supports both clicks and holds:
// Timeout, started on mousedown, triggers the beginning of a hold
var holdStarter = null;
// Milliseconds to wait before recognizing a hold
var holdDelay = 500;
// Indicates the user is currently holding the mouse down
var holdActive = false;
// MouseDown
function onMouseDown(){
// Do not take any immediate action - just set the holdStarter
// to wait for the predetermined delay, and then begin a hold
holdStarter = setTimeout(function() {
holdStarter = null;
holdActive = true;
// begin hold-only operation here, if desired
}, holdDelay);
}
// MouseUp
function onMouseUp(){
// If the mouse is released immediately (i.e., a click), before the
// holdStarter runs, then cancel the holdStarter and do the click
if (holdStarter) {
clearTimeout(holdStarter);
// run click-only operation here
}
// Otherwise, if the mouse was being held, end the hold
else if (holdActive) {
holdActive = false;
// end hold-only operation here, if desired
}
}
// Optional add-on: if mouse moves out, then release hold
function onMouseOut(){
onMouseUp();
}
Here's a demo: http://jsfiddle.net/M7hT8/1/
Originally based on daveyfaherty's solution.
I know this question is from a while ago, but I'm sharing my solution for anyone who finds this via a search.
//last mouse coordinate
var mouseX = 0;
//epsilon interval
var mouseEps = 10;
function mouseDownHandler(e) {
e.preventDefault();
mouseX = e.clientX;
};
function mouseUpHandler(e) {
e.preventDefault();
if (Math.abs((mouseX - e.clientX)) < mouseEps) {
clickHandler(e);
}
};
function clickHandler(e) {
};

Javascript timeout when no actions from user for specified time

I want to call a js function when there is no activity from user on the web page for specified amount of time. If there is activity from user then reset timeout. I tried to search but couldn't find anything in particular. I am familiar with setTimeout() and clearTimeout() and how they work. What I am looking for is where/how to monitor for user activity. Is there any event in which I can set and clear timer?
Thank you.
Edit #1:
This webpage has one input text box & one button. It's kind of regular chat page. When I say no user activity, I mean that the user has not typed anything in text box or has not pressed any button for specified amount of time. And one more thing that it is targeted for touch based smartphone devices.
Edit #2:
Thank you everyone for suggestions. I've implemented solution based on more than one answers provided. So I will give upvote to all answers that I've found helpful instead of accepting one as answer.
// Using jQuery (but could use pure JS with cross-browser event handlers):
var idleSeconds = 30;
$(function(){
var idleTimer;
function resetTimer(){
clearTimeout(idleTimer);
idleTimer = setTimeout(whenUserIdle,idleSeconds*1000);
}
$(document.body).bind('mousemove keydown click',resetTimer); //space separated events list that we want to monitor
resetTimer(); // Start the timer when the page loads
});
function whenUserIdle(){
//...
}
Edit: Not using jQuery for whatever reason? Here's some (untested) code that should be cross-browser clean (to a point; doesn't work on IE5 Mac, for example ;):
attachEvent(window,'load',function(){
var idleSeconds = 30;
var idleTimer;
function resetTimer(){
clearTimeout(idleTimer);
idleTimer = setTimeout(whenUserIdle,idleSeconds*1000);
}
attachEvent(document.body,'mousemove',resetTimer);
attachEvent(document.body,'keydown',resetTimer);
attachEvent(document.body,'click',resetTimer);
resetTimer(); // Start the timer when the page loads
});
function whenUserIdle(){
//...
}
function attachEvent(obj,evt,fnc,useCapture){
if (obj.addEventListener){
obj.addEventListener(evt,fnc,!!useCapture);
return true;
} else if (obj.attachEvent){
return obj.attachEvent("on"+evt,fnc);
}
}
This calls for a debouncer:
function debounce(callback, timeout, _this) {
var timer;
return function(e) {
var _that = this;
if (timer)
clearTimeout(timer);
timer = setTimeout(function() {
callback.call(_this || _that, e);
}, timeout);
}
}
Used like this:
// we'll attach the function created by "debounce" to each of the target
// user input events; this function only fires once 2 seconds have passed
// with no additional input; it can be attached to any number of desired
// events
var userAction = debounce(function(e) {
console.log("silence");
}, 2000);
document.addEventListener("mousemove", userAction, false);
document.addEventListener("click", userAction, false);
document.addEventListener("scroll", userAction, false);
The first user action (mousemove, click, or scroll) kicks off a function (attached to a timer) that resets each time another user action occurs. The primary callback does not fire until the specified amount of time has passed with no actions.
Note that no global flags or timeout variables are needed. The global scope receives only your debounced callback. Beware of solutions that require maintenance of global state; they're going to be difficult to reason about in the context of a larger application.
Note also that this solution is entirely general. Beware of solutions that apply only to your extremely narrow use case.
Most JavaScript events bubble, so you could do something like the following:
Come up with a list of all the events you'd consider to be "activity from the user" (e.g., click, mousemove, keydown, etc.)
Attach one function as an event listener for all of those events to document (or maybe document.body for some of them; I can't remember if that's an issue or not).
When the listener is triggered, have it reset the timer with clearTimeout/setTimeout
So you'd end up with something like this:
var events = ['click', 'mousemove', 'keydown'],
i = events.length,
timer,
delay = 10000,
logout = function () {
// do whatever it is you want to do
// after a period of inactivity
},
reset = function () {
clearTimeout(timer);
timer = setTimeout(logout, 10000);
};
while (i) {
i -= 1;
document.addEventListener(events[i], reset, false);
}
reset();
Note that there are some issues you'd have to work out with the above code:
It's not cross-browser compatible. It only uses addEventListener, so it won't work in IE6-8
It pollutes the global namespace. It creates a lot of excess variables that might conflict with other scripts.
It's more to give you an idea of what you could do.
And now there are four other answers, but I've already typed it all up, so there :P
You want to monitor events like mousemove, keypress, keydown, and/or click at the document level.
Edit: This being a smartphone app changes what events you want to listen for. Given your textbox and button requirements, I'd listen to oninput and then add the resetTimeout() call to the click handler for your button.
var inactivityTimeout = 0;
function resetTimeout() {
clearTimeout(inactivityTimeout);
inactivityTimeout = setTimeout(inactive, 300000);
}
function inactive() {
...
}
document.getElementById("chatInput").oninput = resetTimeout;
Something like this:
function onInactive(ms, cb){
var wait = setTimeout(cb, ms);
// Bind all events you consider as activity
// Note that binding this way overrides any previous events bound the same wa
// So if you already have events bound to document, use AddEventListener and AttachEvent instead
document.onmousemove = document.mousedown = document.mouseup = document.onkeydown = document.onkeyup = document.focus = function(){
clearTimeout(wait);
wait = setTimeout(cb, ms);
};
}
IE: http://jsfiddle.net/acNfy/
Activity in the bottom right frame will delay the callback.
I'm using a nifty little 'delay' method for this that I found in this thread
var delay = (function(){
var timer = 0;
return function(callback, ms){
clearTimeout (timer);
timer = setTimeout(callback, ms);
};
})();
use like
delay(function(){ doSomethingWhenNoInputFor400ms(); },400);
Also, take a look at jQuery idleTimer plugin from Paul Irish (jquery.idle-timer.js). It was based on Nicholas C. Zakas' Detecting if the user is idle with JavaScript and YUI 3 article (idle-timer.js).
It looks at similar events to the other answers, plus a few more.
events = 'mousemove keydown DOMMouseScroll mousewheel mousedown touchstart touchmove';
// activity is one of these events

capture multiple "onkeydown" and wait until "onkeyup" to execute

In my web app, I use the onkeydown event to capture key strokes.
For example, I capture the 'j' key to animate a scroll down the page (and do some other stuff meanwhile).
My problem is the user might keep the 'j' key down to scroll further down the page (this is equivalent to fast multiple key strokes).
In my app, this result in a series of animations that doesn't look that good.
How can I know when the key has been released, and know the amount of key stokes I should have captured? This way I could run one long animation instead of multiple short ones.
Building on #JMD:
var animate = false;
function startanimation()
{
animate = true;
runanimation();
}
function stopanimation()
{
animate = false;
}
function runanimation()
{
if ( animation_over )
{
if ( !animate )
{
return;
}
return startanimation();
}
// animation code
var timeout = 25;
setTimeout(function(){runanimation();},timeout);
}
document.onkeydown = startanimation;
document.onkeyup = stopanimation;
You'll need to add some checks for starting/ending animations, however.
Edit: added a return to the JS; would've recursed endlessly.
Rather than trying to stack up the animations, you could start an animation on keyDown, and if at the end of the animation you haven't yet received keyUp then start another animation. As soon as you reach the end of an animation and you do have keyUp then you're done.
setTimeout returns a timer ID. So what I would do is after you receive a keyDown event, I would start a timer with a very short wait period, like so:
var globalTimerId = -1;
var keyDownCount = 0;
function handleKeyDown(e) {
if (globalTimerId != -1) {
clearTimeout(globalTimerId);
keyDownCount++;
}
/* 500 means 1/2 a second, adjust for your needs */
globalTimerId = setTimeout(handleKeyDownAfterWait, 500);
keyDownCount = 1;
}
function handleKeyDownAfterWait() {
globalTimerId = -1;
/* keyDownCount will have the number of times handleKeyDown was called */
}
So the idea is that each time a keyDown event is received, the timer is cleared (assuming it hasn't elapsed yet) and restarted. If the timer expires, the user has let go of the key and you can handle that in handleKeyDownAfterWait.
There may be other more elegant solutions with jQuery or another JS library however. This is just something quick and dirty (and possibly buggy ;))
you can try using my repsonsiveness plugin see:
http://notetodogself.blogspot.com/2008/12/jquery-responsiveness-plugin-for-fast.html
see the demo here:
http://doesthatevencompile.com/current-projects/jquery.responsiveness/
the one where you type stuff fast. you can adapt that to your needs. like so:
the animation event will be bound with the plugin, and execute when the user stops doing something fast. you can count how many times he does the fast thing by normal binding of the event.

Categories

Resources