2

I have a situation in which I click the button, and it does some action like the following:

var pos = 1;

$('.right').click(function(){    
    $('.box').css({'left': pos++ });
});

$('.left').click(function(){    
    $('.box').css({'left': pos-- });
});
.box{
    background-color: gray;
    height:20px;
    width: 20px;
    left: 0;
    position: relative;
    margin-top: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<button class="left" button-held>Left</button>
<button class="right" button-held>Right</button>
<div class="box"></div>

What I need is that when the user clicks on the button and leave it pressed, that it do this action multiple times until the button is released.

Any ideas on how to do this? I have already search the internet for a while but it's quite difficult to find an answer as there are lots of questions about events, also I found an Angular solution if someone wants to check it out (Angular solution).

Thanks in advance.

2 Answers 2

7

Instead of listening for click, do this:

  1. Listen for mousedown and mouseup.
  2. On mousedown, perform the action and set a timer to perform it again in a moment via setTimeout. Remember the timer handle (the return value).
  3. When the timer goes off and you do the action again, schedule it to happen again in a moment, again via setTimeout, again remembering the handle.
  4. When you see mouseup, cancel the outstanding timer using the handle with clearTimeout.

Example:

var pos = 1;
var handle = 0;

function move(delta) {
    $('.box').css({'left': pos += delta });
}
function moveRight() {
    move(1);
    clearTimeout(handle); // Just in case
    handle = setTimeout(moveRight, 50);
}
function moveLeft() {
    move(-1);
    clearTimeout(handle); // Just in case
    handle = setTimeout(moveLeft, 50);
}

$('.right').on("mousedown", moveRight);
$('.left').on("mousedown", moveLeft);
$('.left, .right').on("mouseup", function() {
    clearTimeout(handle);
    handle = 0;
});
.box{
    background-color: gray;
    height:20px;
    width: 20px;
    left: 0;
    position: relative;
    margin-top: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<button class="left" button-held>Left</button>
<button class="right" button-held>Right</button>
<div class="box"></div>


In a comment you've said:

I just noticed a bug within this, if I leave one of the buttons pressed, and then slide out of it while still keeping it pressed and then releasing it, it move the box indefinitely until you press again one of the buttons, do you maybe know why this is happening?

It's because we're only listening for mouseup on the button, so when you release it, we don't get that event. Silly mistake on my part. :-)

Two solutions:

  1. Either listen for mouseup on document, or

  2. Listen for mouseleave on the button as well as mouseup.

I think #1 is probably best, especially since when I just tried it on Chrome, it even gracefully handled the case where I pressed the mouse down over the button, then slid it right out of the browser window entirely (!) and released it. Chrome still gave us the mouseup on `document. :-)

Implementing #1 is just a matter of hooking mouseup on document instead of .left, .right:

var pos = 1;
var handle = 0;

function move(delta) {
    $('.box').css({'left': pos += delta });
}
function moveRight() {
    move(1);
    clearTimeout(handle); // Just in case
    handle = setTimeout(moveRight, 50);
}
function moveLeft() {
    move(-1);
    clearTimeout(handle); // Just in case
    handle = setTimeout(moveLeft, 50);
}

$('.right').on("mousedown", moveRight);
$('.left').on("mousedown", moveLeft);
// ONLY CHANGE is on the next line
$(document).on("mouseup", function() {
    clearTimeout(handle);
    handle = 0;
});
.box{
    background-color: gray;
    height:20px;
    width: 20px;
    left: 0;
    position: relative;
    margin-top: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<button class="left" button-held>Left</button>
<button class="right" button-held>Right</button>
<div class="box"></div>

Implementing #2 is just a matter of adding mouseleave to the mouseout handler; but note that the button retains its "pushed" appearance even though we stop doing the movement as soon as the mouse leaves the button:

var pos = 1;
var handle = 0;

function move(delta) {
    $('.box').css({'left': pos += delta });
}
function moveRight() {
    move(1);
    clearTimeout(handle); // Just in case
    handle = setTimeout(moveRight, 50);
}
function moveLeft() {
    move(-1);
    clearTimeout(handle); // Just in case
    handle = setTimeout(moveLeft, 50);
}

$('.right').on("mousedown", moveRight);
$('.left').on("mousedown", moveLeft);
// ONLY CHANGE is on the next line
$('.left, .right').on("mouseup mouseleave", function() {
    clearTimeout(handle);
    handle = 0;
});
.box{
    background-color: gray;
    height:20px;
    width: 20px;
    left: 0;
    position: relative;
    margin-top: 20px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<button class="left" button-held>Left</button>
<button class="right" button-held>Right</button>
<div class="box"></div>

Sign up to request clarification or add additional context in comments.

5 Comments

You beat me to it :)
Thank you so much, just what I was looking for. I have a couple of questions about the handle variable, what value does it return? Does that means that every 50ms the function will return a different unique value? Or it will be always the same value until it is stopped with the ClearInterval(sameValueAsReturned) ?
@Leo: setTimeout returns a timer handle (a number, on browsers). Yes, it's a new handle each time. You could use setInterval instead, which sets a recurring timer. I tend to prefer using setTimeout and scheduling the next call from the current call.
@T.J.Crowder hi, I just noticed a bug within this, if I leave one of the buttons pressed, and then slide out of it while still keeping it pressed and then releasing it, it move the box indefinitely until you press again one of the buttons, do you maybe know why this is happening? Any help will be much appreciated, thanks!
Nice! At the end I used the second option, only because the first option you could still select the text from the button, and drag it out of the box causing that behavior. Thanks again man!
1

Something like this? Here you go

var pos = 1;

$('.right').mousedown(function(){    
    myVar = setInterval(function(){ $('.box').css({'left': pos++ }); }, 100);
});
$('.right').mouseup(function(){    
    clearInterval(myVar);
});
$('.left').mousedown(function(){    
    myVar = setInterval(function(){ $('.box').css({'left': pos-- }); }, 100);
});

$('.left').mouseup(function(){    
    clearInterval(myVar);
});

4 Comments

Probably best to declare myVar. Implicit globals are a Bad Thing™. :-)
Didn't occur to me to use setInterval inside those events. Thanks !!
@T.J.Crowder I was amazed that it compiles, doesn't every variable needs to have a var for it? Otherwise what could happen?
@Leo: Welcome to legacy JavaScript. :-) In "loose mode," assigning to an undeclared identifier creates a global variable. They fixed that by introducing "strict mode" in ES5. More in a post on my anemic little blog: The Horror of Implicit Globals

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.