Long click on button creates many instant clicks - javascript

Basically I'm trying to make a button to be performed again many times as long as the mouse is down on that button.
I need this for buttons with sliders. Right now, I click on a button for example "Increase Slider" and the slider is increased by 1 step, but now I want to be able to increase the slider many steps if I long press on that button.
How do I do that?

Your do loop runs as many times as it can in 1000 ms, and the mouseleave and mouseup handlers never get a chance to run because their events are sitting in the message queue waiting for the mousedown handler to finish running through that loop.
The loop sets up a couple thousand timeouts, to be run at least 200 ms later. Those timeouts don't actually do anything given the code you posted, because window's click handler is being called, not your button's.
The mouseleave and mouseup handlers essentially do nothing, because start will be reset to a valid time before ever being checked.
So how do we fix it?
There are two delays we want: the 1000 ms delay between the initial click and the first time the slider increases, and the 200 ms delay between slider increases. If the user cancels during the first 1000 ms, we'll count that as a single click. If the user cancels after the repetition starts, we shouldn't count that as a click. (We'll define "canceling" as releasing the mouse button or moving the cursor off the button. This means pressing the mouse button over the UI button and moving the cursor off will count as a click, but the code will be simpler.)
We can set up the delays by setting up a timeout that, after 1000 ms, sets up an interval that, every 200 ms, increases the slider. We won't be using the click event for the slider increase because of the last line of the spec:
If the user cancels after the repetition starts, we shouldn't count that as a click.
So we'll give the slider-increase code its own function, increaseSlider() (which is good practice anyway):
function startLongClick (e) {
window.setTimeout(() => {
increaseSlider();
window.setInterval(() => {
increaseSlider();
}, 200);
}, 1000);
}
$('#button').on('mousedown', startLongClick);
We put the first call to increaseSlider() in the timeout so the slider first increases 1000 ms after the initial click, not 1200. We use arrow functions in the timeout and interval because arrow functions don't redefine this, so we'd be able to refer to the triggering <button> if necessary.
I can't stop it!
As the code is now, a single click on the button will start the whole long-click process, with no way of stopping it. Stopping the process means stopping the timeout and interval, which we can do with window.clearTimeout() or window.clearInterval() (they're the same function; don't tell anybody). We'll need to hang on to the IDs setTimeout() and setInterval() give us, and clear them in the mouseup and mouseleave handlers:
let intervalId;
let timeoutId;
function startLongClick (e) {
timeoutId = window.setTimeout(() => {
increaseSlider();
intervalId = window.setInterval(() => {
increaseSlider();
}, 200);
}, 1000);
}
function cancelLongClick () {
window.clearInterval(intervalId);
window.clearTimeout(timeoutId);
}
$('#button').on('mousedown', startLongClick);
$('#button').on('mouseup', cancelLongClick);
$('#button').on('mouseleave', cancelLongClick);
What about the short click?
Now the button's doing what we want it to do, with one exception: a short click doesn't do anything, because we're not using the click handler and the timeout is being cleared before increaseSlider() is ever called. A short click should be registered if a canceling event is fired after the mousedown event but before the timeout fires. Since timeoutId is undefined before the mousedown event and we don't need it once the timeout fires, we can assign undefined to it in the timeout and use it to determine whether we should register a short click:
let intervalId;
let timeoutId;
function startLongClick (e) {
timeoutId = window.setTimeout(() => {
timeoutId = undefined;
increaseSlider();
intervalId = window.setInterval(() => {
increaseSlider();
}, 200);
}, 1000);
}
function cancelLongClick () {
window.clearInterval(intervalId);
if (timeoutId) {
increaseSlider();
window.clearTimeout(timeoutId);
timeoutId = undefined;
}
}
$('#button').on('mousedown', startLongClick);
$('#button').on('mouseup', cancelLongClick);
$('#button').on('mouseleave', cancelLongClick);
We set timeoutId to undefined in the short-click code as well. Otherwise, after short-clicking, an increase would trigger every time you mouse out of the button.
More buttons!
The code works now, but requires two global variables and is hard-coded for a specific button. Let's turn it into a general-purpose jQuery plugin*:
(($) => {
$.fn.repeatingClick = function (callback, delay = 500, interval = 200) {
return this.each(function () {
let intervalId;
let timeoutId;
function startLongClick (e) {
timeoutId = window.setTimeout(() => {
timeoutId = undefined;
callback.call($(this), e);
intervalId = window.setInterval(() => {
callback.call(this, e);
}, interval);
}, delay);
}
function cancelLongClick (e) {
window.clearInterval(intervalId);
if (timeoutId) {
callback.call(this, e);
window.clearTimeout(timeoutId);
timeoutId = undefined;
}
}
$(this).on('mousedown', startLongClick);
$(this).on('mouseup', cancelLongClick);
$(this).on('mouseleave', cancelLongClick);
});
}
})(jQuery);
function modifySlider (e) {
let modifier = Number($(this).data('change'));
$('progress').attr('value', Number($('progress').attr('value')) + modifier);
}
$('button').repeatingClick(modifySlider);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="dec" data-change="-1">−</button>
<progress value="25" max="50"></progress>
<button id="inc" data-change="1">+</button>
What's changed?
Replaced calls to increaseSlider() with a callback parameter and callback.call($(this), e). This way, any function can be used as the callback, and since we used arrow functions in the timeout, we're able to use Function.call with this to access the triggering element in the callback.
Parameterized the delays in the timeout and interval into delay and interval, for more general use.
Stuck the whole thing in a new jQuery function, $.repeatingClick(). Since jQuery objects can represent collections as well as individual elements, we wrap the original code in a call to $.each() to access each element individually. We also return the jQuery object in the usual style.
The rest is specific to this application: two buttons to modify the value of a (<progress>) 'slider', using custom data- attributes for the actual amounts so we can give both the same code.
*I've never written a jQuery plugin before; most of the code surrounding the core logic came straight from jquery-longpress, a jQuery plugin that does almost what OP wants.

Try using intervals instead of manually calculating time. Check this out:
var value = 0
var addval;
var press = false;
$('#button').on('mousedown', function (e) {
press = true;
increaseValue();
return false;
});
$('#button').on('mouseleave', function (e) {
clearInterval(addval);
return false;
});
$('#button').on('mouseenter', function(e) {
if (press)
increaseValue();
});
$('#button').on('mouseup', function (e) {
press = false;
clearInterval(addval);
return false;
});
function increaseValue() {
addval = setInterval(function(){
value++;
$("#counter").text(value);
}, 100);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<button id="button">Press me</button>
<div id="counter">0</div>
You can adjust the speed by changing interval time.

Related

jQuery: Delay a function from being used again once fired for a set time

I want my function to fire on scroll, but then wait 250ms until it may fire again.
function myFunction() {
console.log('hello');
}
$(window).on('scroll', function() {
myFunction();
});
I have tried a timeout:
$(window).on('scroll', function() {
setTimeout(function() {
myFunction();
}, 250);
});
However this method delays for 250ms before firing the function.
Based on this answer, you can add a flag so that additional events are not fired, then clear that flag using a timeout.
Note that this will lose/drop events within the timeout, so should not be used for something like user keyboard input; where debounce would be more suitable.
var active = false;
$(window).on('scroll', function() {
if (active) return;
active = true;
myFunction();
setTimeout(function() {
active = false;
}, 250);
});
function myFunction() { console.log("scroll"); }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div style='height:30000px;'>
something to scroll
</div>
This is a basic throttle implementation. Improvements would be to make it modular / namespace'd and/or store the "active" flag on the element itself; so that it the same event can be reused for multiple elements and doesn't create lots of global variables.
Simply call your function once outside of setTimeout.
$(window).on('scroll', function() {
myFunction(); // call once
setTimeout(function() {
myFunction();
}, 250); // repeat call after 250ms
});
Use setInterval instead of setTimeout if you want repeated calls every 250ms.

JavaScript long click auto

I have trouble adding a long drop to an item on the page
I have a div and I need to set a long click with time outside auto as a human
I want is like element.click() but with a long timeout... Can you help me with example code?
<div>my div</div>
$('div').mousedown(function(){}
Suppose you want to abort the operation if mousedown event lasts less then, for example, 500 milliseconds? Here it is:
let timerId;
$('div').mousedown(function(){
timerId = setTimeout( function() {alert("hi!"); }, 500);
});
$('div').mouseup( function () {
clearTimeout(timerId);
});
set duration for 1 second 1000ms
var pressTimer;
$("div").mousedown(function(){
// Set timeout
pressTimer = window.setTimeout(function() { ... Your Code ...},1000);
}).mouseup(function(){
// clear timeout
clearTimeout(pressTimer);
});

Set interval works only first time. Is there any solution?

I want to run the function continuously. But it only works first time properly. Is there any solution for working this function continuously?
$(document).ready(function() {
setInterval(() => {
$('#open-band').trigger('click');
setTimeout(() => {
$('#close-band').trigger('click');
}, 50000);
}, 15000);
});
If the code inside the setInterval takes longer than the time you have set it will create another process before the function finishes messing everything up. So choosing setTimeout is actually better.
To make a function loops in setTimeout use a following syntax:
function function1() {
// something here
}
function runner() {
function1();
setTimeout(function() {
runner();
}, time);
}
runner();
Given the comment under the question explaining your goal:
I want to trigger a button to show a div after 15 secs when the page is loaded, and 50 secs later another trigger for closing the div. & I want to run this continuously
I would suggest that you chain setTimeout() calls instead of using setInterval() which will cause the events to overlap and become a mess. I'd also suggest that you call show() and hide() directly on the required elements instead of faking click events in the DOM. Try this:
function openBand() {
$('#yourElement').show();
setTimeout(closeBand, 50000);
}
function closeBand() {
$('#yourElement').hide();
setTimeout(openBand, 15000);
}
$(document).ready(function() {
setTimeout(openBand, 15000);
// or just call closeBand() here directly, if the element starts as hidden
});
You should change your current function with this one
$(document).ready(function() {
setInterval(() => {
$('#open-band').trigger('click');
}, 15000);
setTimeout(() => {
$('#close-band').trigger('click');
}, 50000);
});

Do function with timeout while togglebutton is pressed

I want to run a function with a timeout of 2000 ms. The function should just run while toggleButton is pressed.
When I run this function my CPU explodes:
do {
setTimeout(function () {
me.pushMockData();
}, 2000);
}
while (liveButton.getPressed() != false);
Your CPU explode because you create Timeout again, again and again really fast in your loop when button is pressed. If you want to run your function every 2 second :
You should test if the the button is pressed.
Use setInterval
Inside setInterval, check if the button is still pressed, if not, clearInterval
https://www.w3schools.com/jsref/met_win_setinterval.asp
If you just want to active your function 2 second later, use setTimeout inside if.
You are going about this all wrong, keep things simple and attach an event listener to the button and call setTimeout then.
HTML
<button id="my_button">Click Me</button>
JavaScript
document.getElementById('my_button').addEventListener('click', function () {
setTimeout(function () {
alert('Clicked!');
}, 2000);
});
JSFiddle
Live Example
It's because you are constantly calling the setTimeout function in the while loop.
var timeout;
if (liveButton.getPressed())
{
if (typeof timeout !== 'undefined') {
timeout = setTimeout(function () {
me.pushMockData();
}, 2000);
} else {
clearTimeout(timeout);
}
}
If you can catch the onPress and onRelease event, it would be better if you do following
onPress
var myTimeout = setTimeout( ... , 2000 )
onRelease
clearTimeout(myTimeout)
example: https://jsfiddle.net/jfefL4oo/1/

Google Map event bounds_changed triggered multiple times when dragging

I have a google map with markers. I want my markers to be refreshed when the map is moved/zoomed...
Google recommend to use the event bounds_changed for that, but when I move the map, the event is triggered for each pixel that I move the map. I want the map to be refreshed only when the user stopped moving the map, i.e. when he released the mouse button after dragging.
How can I do that ?
It turns out it was a reported bug: http://code.google.com/p/gmaps-api-issues/issues/detail?id=1371.
The Google team recommend to use the event "idle". For example :
google.maps.event.addListener(map, 'idle', function() {
});
While the selected answer is best for most circumstances. If you want to control the delay yourself, you can simply use something like;
var mapupdater;
{....}
google.maps.event.addListener(map, "bounds_changed", mapSettleTime);
function mapSettleTime() {
clearTimeout(mapupdater);
mapupdater=setTimeout(getMapMarkers,500);
}
Add a timeout, that runs your code 500ms after the event fires, each time the event fires clear the timeout and create a new one.
google.maps.event.addListener(map, 'bounds_changed', (function () {
var timer;
return function() {
clearTimeout(timer);
timer = setTimeout(function() {
// here goes an ajax call
}, 500);
}
}()));
You should check how a debounce function works. A nice article by Taylor Case define it as follows:
This function is built in order to limit the amount of times a
function is called — scroll events, mousemove events, and keypress
events are all great examples of events that we might want to capture,
but can be quite taxing if we capture them every single time they
fire.
So you define the function somewhere in your code:
function debounce(fn, time) {
let timeout;
return function() {
const args = arguments;
const functionCall = () => fn.apply(this, args);
clearTimeout(timeout);
timeout = setTimeout(functionCall, time);
}
}
Then you just use that function when adding your listener:
google.maps.event.addListener(myMap, 'bounds_changed', debounce(() => { /* Do something here */ }, 250));
It seems that 250 ms is a good frequency to use here.
try using both zoom_changed and dragend
Here is a little snippet that will remove all redundant 'bound_changed' call's:
var timeout;
google.maps.event.addListener(map, 'bounds_changed', function () {
window.clearTimeout(timeout);
timeout = window.setTimeout(function () {
//do stuff on event
}, 500);
}); //time in ms, that will reset if next 'bounds_changed' call is send, otherwise code will be executed after that time is up

Categories

Resources