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) {
};
Related
I wanna achieve an effect where users can drag the screen to switch pages. I want it to print the mouse position first.
Here's a method:
dragging(ev) {
console.log(ev)
},
The dragging method is evoked when I press and move my mouse.
this.$refs.screen.addEventListener("mousedown", (ev)=>{
this.$refs.screen.addEventListener("mousemove", this.dragging)
})
But when I pass some arguments into it, the dragging method can't be evoked when I do the same thing.
this.$refs.screen.addEventListener("mousedown", (ev)=>{
this.$refs.screen.addEventListener("mousemove", this.dragging(ev))
})
I've tried several methods but I still can't solve the problem.
With each .addEventListener() you are creating a new EventListener ontop of all already added EventListeners. Your code would generate a new EventListerner when the mousebutton was pressed, which would create endless amounts of EventListeners on runtime.
Your second attempt doesn't work, as the EventListener got console.log(ev) as the callback function to execute when ever the mouse was moved.
The Approach would be to have a flag, that indicates if the mousebutton is pressed and in your mousemove EventListener you would check if the flag is true and then operate your desired logic.
// Somewhere in your data property of your component
isMouseDown = false;
// When the mousebutton is pressed, we set the flag to true
this.$refs.screen.addEventListener("mousedown", (ev)=>{
this.isMouseDown = true;
});
// When the mousebutton is released, we set the flag to false
this.$refs.screen.addEventListener("mouseup", (ev)=>{
this.isMouseDown = false;
});
// .addEventListener expects a function, that it can execute if the Event occurs.
this.$refs.screen.addEventListener("mousemove", (ev) => {
this.dragging(ev, arg1, arg2, arg3);
});
dragging(ev, arg1, arg2, arg3) {
// We only execute dragging if the isMouseDown flag is true, otherwise we quit
if(!this.isMouseDown) return;
// You operation logic
console.log(ev)
},
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);
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
In my form I have a set of input boxes where a user can input a value.
On change of one of these boxes, the form automatically gets submitted.
The problem now is however that a user stays in the last field, takes the mouse and presses the OK button (of another form) without leaving the textbox first. The change event doesn't get triggered and the old, incorrect values get passed to the next page.
I want to trigger the onchange event after a few miliseconds of inactive keyboard. Just like most autocomplete plugins do.
I think I could implement a timer that starts timing the moment you enter an input field and gets resetted everytime a keystroke is handled and then when it reaches zero the onchange event gets triggered.
I'm not up for re-inventing the wheel and was wondering if such a function is available somewhere.
Suggestions?
I had a similar problem and created a jQuery plugin currently in use in an internal application. It should trigger the change event after the user is done typing.
If you are not using jQuery, the code is still adaptable to anything else.
jQuery.fn.handleKeyboardChange = function(nDelay)
{
// Utility function to test if a keyboard event should be ignored
function shouldIgnore(event)
{
var mapIgnoredKeys = {
9:true, // Tab
16:true, 17:true, 18:true, // Shift, Alt, Ctrl
37:true, 38:true, 39:true, 40:true, // Arrows
91:true, 92:true, 93:true // Windows keys
};
return mapIgnoredKeys[event.which];
}
// Utility function to fire OUR change event if the value was actually changed
function fireChange($element)
{
if( $element.val() != jQuery.data($element[0], "valueLast") )
{
jQuery.data($element[0], "valueLast", $element.val())
$element.trigger("change");
}
}
// The currently running timeout,
// will be accessed with closures
var timeout = 0;
// Utility function to cancel a previously set timeout
function clearPreviousTimeout()
{
if( timeout )
{
clearTimeout(timeout);
}
}
return this
.keydown(function(event)
{
if( shouldIgnore(event) ) return;
// User pressed a key, stop the timeout for now
clearPreviousTimeout();
return null;
})
.keyup(function(event)
{
if( shouldIgnore(event) ) return;
// Start a timeout to fire our event after some time of inactivity
// Eventually cancel a previously running timeout
clearPreviousTimeout();
var $self = $(this);
timeout = setTimeout(function(){ fireChange($self) }, nDelay);
})
.change(function()
{
// Fire a change
// Use our function instead of just firing the event
// Because we want to check if value really changed since
// our previous event.
// This is for when the browser fires the change event
// though we already fired the event because of the timeout
fireChange($(this));
})
;
}
Usage:
$("#my_input").handleKeyboardChange(300).change(function()
{
// value has changed!
});
Does it not work to do an onBlur, so both when the user moves to the next field or clicks something else, the value is saved?
I don't know that such a solution would be considered "re-inventing" anything. As you said, it sounds to be nothing more than a simple setTimeout once the page loads. After about 3,000 milliseconds, it runs form.submit().
I would probably restart the count-down with each keystroke too, to give the user enough time to make their entry.
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.