7

I don't know why this appears to be so difficult to figure out. I want to be able to execute code when a key is pressed and held but only once. Instead when I use onkeypress or onkeydown the function that I bound gets executed repeatedly which is not what I want. How can I have the handler be executed just once when the key is held down?

Note: I don't want to embed logic into the function that will limit its execution, I want it not to be firing the event more than once no matter how long I hold the key.

EDIT I

Here is the demo and the code

HTML

<div id="counter">0</div>

JS

var counter = 0,
    div = document.getElementById('counter');

document.body.onkeypress = function(){
    div.innerHTML = counter++;
}

Notice how when you press and hold any key the counter keeps going, I want it to count just once no matter how long I hold the key, and keep in mind the notice from above.

EDIT II

Sorry forgot to mention removing the listener is not acceptable, I need to increase the counter by 1 every time a key is pressed but no matter how long it's held.

Community
  • 1
  • 1
php_nub_qq
  • 15,199
  • 21
  • 74
  • 144

4 Answers4

10

You do have to use logic to avoid repetitive events on a key being pressed, because there's no specific and compatible event for key being just pressed.

More specifically, the easiest solution is to store a boolean, setting it true on key up, false on key down (after having done your action), and ignoring the key down event while it's false:

(function(){
    var shouldHandleKeyDown = true;
    document.onkeydown = function(){
      if (!shouldHandleKeyDown) return;
      shouldHandleKeyDown = false;
      // HANDLE KEY DOWN HERE
    }
    document.onkeyup = function(){
      shouldHandleKeyDown = true;
    }
})();

Demonstration

EDIT for 2019

Now that IE is dead, you can also use the event.repeat property, which is true when the event is a repetition.

Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
  • I was really hoping that was not the case, I swear I can remember I've done this in the past but I just can't remember how... – php_nub_qq Dec 25 '14 at 08:51
  • @php_nub_qq It's not so hard to do. I've added a demonstration with a counter. – Denys Séguret Dec 25 '14 at 08:55
  • @php_nub_qq I also recommend to use `++counter` instead of `counter++` so you can see the change in the HTML right after you press the key – Jan Turoň Dec 25 '14 at 09:00
  • I know it is not hard but it's consuming resources every time the event is fired, I'm looking to eliminate that : – php_nub_qq Dec 25 '14 at 09:00
  • @php_nub_qq No, that consumption is so low you really should not take it into account. If you have performances problem, profile your application, you'll see that's not where you need to put your attention. – Denys Séguret Dec 25 '14 at 09:11
  • This is unnecessarily complicated when you can just use `event.repeat` for the logic. – Kankaristo Apr 02 '19 at 11:09
  • 1
    @Humpparitari In 2014, when I answered, IE was still relevant. – Denys Séguret Apr 02 '19 at 13:32
  • @DenysSéguret, fair enough, downvote removed. (Edit: when/if the vote lock is removed, didn't know that was a thing) – Kankaristo Apr 02 '19 at 17:22
5

The easiest way to handle this is by using the repeat property in the event:

// I'd recommend using addEventListener instead, but
// this is as close to the original code as possible
document.body.onkeypress = function (event) {
    if (!event.repeat) {
        div.innerHTML = counter++;
    }
}

event.repeat is false for the very first event, and true for repeated events (the ones that are fired when you hold down a key).

Another option is to use keyup, which is always only used once, since you can't "hold up" a key, so it's never repeated (but keyup is a bad choice for e.g. buttons, because it can break keyboard navigation compared to keypress).

Kankaristo
  • 2,515
  • 2
  • 20
  • 16
  • This is such a better way in general to handle this without creating your own state to keep track of what has been pressed. – Justin Harris Aug 10 '20 at 03:10
  • This one worked for me, except you should use onkeydown as onkeypress is deprecated – Low Dec 11 '20 at 17:54
2

JSFiddle: http://jsfiddle.net/ts7w58od/
1. Bind listener
2. Unbind when event is fire.

var element = document.getElementById('target'),
    once = function () {
        console.log('once');
        element.removeEventListener('keypress', once);
    };

element.addEventListener('keypress', once, false);
sergolius
  • 428
  • 3
  • 8
0

This is a modified version of Denys Séguret answer

let shouldHandleKeyDown = true;
let n = 0;

document.addEventListener('keydown', function() {
  if (!shouldHandleKeyDown) return;
  shouldHandleKeyDown = false;
  
  document.getElementById('counter').innerHTML = ++n;
});

document.addEventListener('keyup', function () {
  shouldHandleKeyDown = true;
});
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Counter</title>
  </head>
  
  <body>
    Counter : <span id=counter>0</span>
  </body>
</html>
Advaith
  • 2,490
  • 3
  • 21
  • 36