How to detect if multiple keys are pressed at once using JavaScript?
Asked Answered
A

20

228

I'm trying to develop a JavaScript game engine and I've came across this problem:

  • When I press SPACE the character jumps.
  • When I press the character moves right.

The problem is that when I'm pressing right and then press space, the character jumps and then stops moving.

I use the keydown function to get the key pressed. How can I check if there are multiple keys pressed at once?

Amedeo answered 5/3, 2011 at 11:7 Comment(1)
Here's a demo of a web page that automatically prints a list of all keys that are pressed: https://mcmap.net/q/120047/-get-a-list-of-all-currently-pressed-keys-in-javascript-duplicateFlexuosity
C
411

Note: keyCode is now deprecated.

Multiple keystroke detection is easy if you understand the concept

The way I do it is like this:

var map = {}; // You could also use an array
onkeydown = onkeyup = function(e){
    e = e || event; // to deal with IE
    map[e.keyCode] = e.type == 'keydown';
    /* insert conditional here */
}

This code is very simple: Since the computer only passes one keystroke at a time, an array is created to keep track of multiple keys. The array can then be used to check for one or more keys at once.

Just to explain, let's say you press A and B, each fires a keydown event that sets map[e.keyCode] to the value of e.type == keydown, which evaluates to either true or false. Now both map[65] and map[66] are set to true. When you let go of A, the keyup event fires, causing the same logic to determine the opposite result for map[65] (A), which is now false, but since map[66] (B) is still "down" (it hasn't triggered a keyup event), it remains true.

The map array, through both events, looks like this:

// keydown A 
// keydown B
[
    65:true,
    66:true
]
// keyup A
// keydown B
[
    65:false,
    66:true
]

There are two things you can do now:

A) A Key logger (example) can be created as a reference for later when you want to quickly figure out one or more key codes. Assuming you have defined an html element and pointed to it with the variable element.

element.innerHTML = '';
var i, l = map.length;
for(i = 0; i < l; i ++){
    if(map[i]){
        element.innerHTML += '<hr>' + i;
    }
}

Note: You can easily grab an element by its id attribute.

<div id="element"></div>

This creates an html element that can be easily referenced in javascript with element

alert(element); // [Object HTMLDivElement]

You don't even have to use document.getElementById() or $() to grab it. But for the sake of compatibility, use of jQuery's $() is more widely recommended.

Just make sure the script tag comes after the body of the HTML. Optimization tip: Most big-name websites put the script tag after the body tag for optimization. This is because the script tag blocks further elements from loading until its script is finished downloading. Putting it ahead of the content allows the content to load beforehand.

B (which is where your interest lies) You can check for one or more keys at a time where /*insert conditional here*/ was, take this example:

if(map[17] && map[16] && map[65]){ // CTRL+SHIFT+A
    alert('Control Shift A');
}else if(map[17] && map[16] && map[66]){ // CTRL+SHIFT+B
    alert('Control Shift B');
}else if(map[17] && map[16] && map[67]){ // CTRL+SHIFT+C
    alert('Control Shift C');
}

Edit: That isn't the most readable snippet. Readability's important, so you could try something like this to make it easier on the eyes:

function test_key(selkey){
    var alias = {
        "ctrl":  17,
        "shift": 16,
        "A":     65,
        /* ... */
    };

    return key[selkey] || key[alias[selkey]];
}

function test_keys(){
    var keylist = arguments;

    for(var i = 0; i < keylist.length; i++)
        if(!test_key(keylist[i]))
            return false;

    return true;
}

Usage:

test_keys(13, 16, 65)
test_keys('ctrl', 'shift', 'A')
test_key(65)
test_key('A')

Is this better?

if(test_keys('ctrl', 'shift')){
    if(test_key('A')){
        alert('Control Shift A');
    } else if(test_key('B')){
        alert('Control Shift B');
    } else if(test_key('C')){
        alert('Control Shift C');
    }
}

(end of edit)


This example checks for CtrlShiftA, CtrlShiftB, and CtrlShiftC

It's just as simple as that :)

Notes

Keeping Track of KeyCodes

As a general rule, it is good practice to document code, especially things like Key codes (like // CTRL+ENTER) so you can remember what they were.

You should also put the key codes in the same order as the documentation (CTRL+ENTER => map[17] && map[13], NOT map[13] && map[17]). This way you won't ever get confused when you need to go back and edit the code.

A gotcha with if-else chains

If checking for combos of differing amounts (like CtrlShiftAltEnter and CtrlEnter), put smaller combos after larger combos, or else the smaller combos will override the larger combos if they are similar enough. Example:

// Correct:
if(map[17] && map[16] && map[13]){ // CTRL+SHIFT+ENTER
    alert('Whoa, mr. power user');
}else if(map[17] && map[13]){ // CTRL+ENTER
    alert('You found me');
}else if(map[13]){ // ENTER
    alert('You pressed Enter. You win the prize!')
}

// Incorrect:
if(map[17] && map[13]){ // CTRL+ENTER
    alert('You found me');
}else if(map[17] && map[16] && map[13]){ // CTRL+SHIFT+ENTER
    alert('Whoa, mr. power user');
}else if(map[13]){ // ENTER
    alert('You pressed Enter. You win the prize!');
}
// What will go wrong: When trying to do CTRL+SHIFT+ENTER, it will
// detect CTRL+ENTER first, and override CTRL+SHIFT+ENTER.
// Removing the else's is not a proper solution, either
// as it will cause it to alert BOTH "Mr. Power user" AND "You Found Me"

Gotcha: "This key combo keeps activating even though I'm not pressing the keys"

When dealing with alerts or anything that takes focus from the main window, you might want to include map = [] to reset the array after the condition is done. This is because some things, like alert(), take the focus away from the main window and cause the 'keyup' event to not trigger. For example:

if(map[17] && map[13]){ // CTRL+ENTER
    alert('Oh noes, a bug!');
}
// When you Press any key after executing this, it will alert again, even though you 
// are clearly NOT pressing CTRL+ENTER
// The fix would look like this:

if(map[17] && map[13]){ // CTRL+ENTER
    alert('Take that, bug!');
    map = {};
}
// The bug no longer happens since the array is cleared

Gotcha: Browser Defaults

Here's an annoying thing I found, with the solution included:

Problem: Since the browser usually has default actions on key combos (like CtrlD activates the bookmark window, or CtrlShiftC activates skynote on maxthon), you might also want to add return false after map = [], so users of your site won't get frustrated when the "Duplicate File" function, being put on CtrlD, bookmarks the page instead.

if(map[17] && map[68]){ // CTRL+D
    alert('The bookmark window didn\'t pop up!');
    map = {};
    return false;
}

Without return false, the Bookmark window would pop up, to the dismay of the user.

The return statement (new)

Okay, so you don't always want to exit the function at that point. That's why the event.preventDefault() function is there. What it does is set an internal flag that tells the interpreter to not allow the browser to run its default action. After that, execution of the function continues (whereas return will immediately exit the function).

Understand this distinction before you decide whether to use return false or e.preventDefault()

event.keyCode is deprecated

User SeanVieira pointed out in the comments that event.keyCode is deprecated.

There, he gave an excellent alternative: event.key, which returns a string representation of the key being pressed, like "a" for A, or "Shift" for Shift.

I went ahead and cooked up a tool for examining said strings.

element.onevent vs element.addEventListener

Handlers registered with addEventListener can be stacked, and are called in the order of registration, while setting .onevent directly is rather aggressive and overrides anything you previously had.

document.body.onkeydown = function(ev){
    // do some stuff
    ev.preventDefault(); // cancels default actions
    return false; // cancels this function as well as default actions
}

document.body.addEventListener("keydown", function(ev){
    // do some stuff
    ev.preventDefault() // cancels default actions
    return false; // cancels this function only
});

The .onevent property seems to override everything and the behavior of ev.preventDefault() and return false; can be rather unpredictable.

In either case, handlers registered via addEventlistener seem to be easier to write and reason about.

There is also attachEvent("onevent", callback) from Internet Explorer's non-standard implementation, but this is beyond deprecated and doesn't even pertain to JavaScript (it pertains to an esoteric language called JScript). It would be in your best interest to avoid polyglot code as much as possible.

A helper class

To address confusion/complaints, I've written a "class" that does this abstraction (pastebin link):

function Input(el){
    var parent = el,
        map = {},
        intervals = {};
    
    function ev_kdown(ev)
    {
        map[ev.key] = true;
        ev.preventDefault();
        return;
    }
    
    function ev_kup(ev)
    {
        map[ev.key] = false;
        ev.preventDefault();
        return;
    }
    
    function key_down(key)
    {
        return map[key];
    }

    function keys_down_array(array)
    {
        for(var i = 0; i < array.length; i++)
            if(!key_down(array[i]))
                return false;

        return true;
    }
    
    function keys_down_arguments()
    {
        return keys_down_array(Array.from(arguments));
    }
    
    function clear()
    {
        map = {};
    }
    
    function watch_loop(keylist, callback)
    {
        return function(){
            if(keys_down_array(keylist))
                callback();
        }
    }

    function watch(name, callback)
    {
        var keylist = Array.from(arguments).splice(2);

        intervals[name] = setInterval(watch_loop(keylist, callback), 1000/24);
    }

    function unwatch(name)
    {
        clearInterval(intervals[name]);
        delete intervals[name];
    }

    function detach()
    {
        parent.removeEventListener("keydown", ev_kdown);
        parent.removeEventListener("keyup", ev_kup);
    }
    
    function attach()
    {
        parent.addEventListener("keydown", ev_kdown);
        parent.addEventListener("keyup", ev_kup);
    }
    
    function Input()
    {
        attach();

        return {
            key_down: key_down,
            keys_down: keys_down_arguments,
            watch: watch,
            unwatch: unwatch,
            clear: clear,
            detach: detach
        };
    }
    
    return Input();
}

This class doesn't do everything and it won't handle every conceivable use case. I'm not a library guy. But for general interactive use it should be fine.

To use this class, create an instance and point it to the element you want to associate keyboard input with:

var input_txt = Input(document.getElementById("txt"));

input_txt.watch("print_5", function(){
    txt.value += "FIVE ";
}, "Control", "5");

What this will do is attach a new input listener to the element with #txt (let's assume it's a textarea), and set a watchpoint for the key combo Ctrl+5. When both Ctrl and 5 are down, the callback function you passed in (in this case, a function that adds "FIVE " to the textarea) will be called. The callback is associated with the name print_5, so to remove it, you simply use:

input_txt.unwatch("print_5");

To detach input_txt from the txt element:

input_txt.detach();

This way, garbage collection can pick up the object (input_txt), should it be thrown away, and you won't have an old zombie event listener left over.

For thoroughness, here is a quick reference to the class's API, presented in C/Java style so you know what they return and what arguments they expect.

Boolean  key_down (String key);

Returns true if key is down, false otherwise.

Boolean  keys_down (String key1, String key2, ...);

Returns true if all keys key1 .. keyN are down, false otherwise.

void     watch (String name, Function callback, String key1, String key2, ...);

Creates a "watchpoint" such that pressing all of keyN will trigger the callback

void     unwatch (String name);

Removes said watchpoint via its name

void     clear (void);

Wipes the "keys down" cache. Equivalent to map = {} above

void     detach (void);

Detaches the ev_kdown and ev_kup listeners from the parent element, making it possible to safely get rid of the instance

Update 2017-12-02 In response to a request to publish this to github, I have created a gist.

Update 2018-07-21 I've been playing with declarative style programming for a while, and this way is now my personal favorite: fiddle, pastebin

Generally, it'll work with the cases you would realistically want (ctrl, alt, shift), but if you need to hit, say, a+w at the same time, it wouldn't be too difficult to "combine" the approaches into a multi-key-lookup.


I hope this thoroughly explained answer mini-blog was helpful :)

Coimbatore answered 16/9, 2012 at 5:35 Comment(42)
I just made a big update to this answer! The keylogger example is more coherent, I updated the formatting so that the "notes" section would be easier to read, and I added a new note about return false vs preventDefault()Coimbatore
What about when you press/hold a key with document in focus, then you click the URL box, then you let go of the key. keyup is never fired, yet the key is up, causing the list to be incorrect. Also vice-versa: key press/hold in URL box, keydown is never fired, then put focus on document and the keydown status isn't on the list. Basically whenever document regains focus you can never be sure of key status.Tantrum
@Tantrum This boils down to having some faith in the intelligence of your users. There's nothing you can do about the focus thing except to empty the keys_down[] array whenever a function is activated. Even flash doesn't have a solution for it. Generally, focus being taken away is unintentional. But, if your user wants to intentionally dick around with the keys, and manages to break something, that's their own fault. Common sense dictates that they won't do it again, because they would want the webapp/page to function properly, hence why they're even using it.Coimbatore
I agree. Your solution is the best considering what browsers give JavaScript. I like how you combine keyup and keydown into one function! I guess my comment was mostly frustration with browsers not giving JavaScript a list of down keys. In GamePad API JavaScript is given the states of buttons pressed so apps don't have to keep track of it themselves, though I'm not sure if GamePad API gives eventListeners for change of state. For security though it should not give access to the list of keys down when not in focus.Tantrum
@Tantrum the "list" is just an array that you keep updated yourself. Other tabs would not be able to access said keys, and if you're worried about key loggers, no amount of js code would prevent an external program from reading your keyboard input. Even if you delete the array, the data is still in memory, and can be read. As for focus, in say, C++, you could detect when the window loses focus and wipe the keysdown[] array when that happens. If there is such an event (unfocus) in Javascript, you could use that to wipe the array every time the window loses focus. This would solve the bug.Coimbatore
There's onblur, the opposite of onfocus. Actually I was thinking of iframes rather than external programs, or are scripts of the parent page shared with iframes? I guess each tab is it's own program instance to prevent cross tab snooping.Tantrum
@Tantrum You should avoid frames like the plague if possible. So many security and design issues with those. If you need dynamic content for example, use a preprocessor like php to generate the page. Window.onblur should be enough to tell the app to reset keys[]. As for someone else framing your page, take a look at Google.com, which includes code to block accessing it through a frame. Also, avoid global variables, and try wrapping your code in an anonymous function.Coimbatore
Also, you could try looking at Google.com's code, which includes a frame blocker. You should also look into anonymous functions of you want to completely block outside access (such as a user via the console) to the variables.Coimbatore
There are a few safeguards when framing another site w3schools.com/tags/att_iframe_sandbox.asp developers.google.com/caja I'll take a look at Google's code about preventing your site from being framed, but usually it's obfuscated via minification. Found this when searching en.wikipedia.org/wiki/Framekiller I take it anonymous functions can't be searched via the console.Tantrum
@Tantrum forgot about the obfuscated code, whoops. As for anon funcs, they take advantage of function scoping to prevent outside access to the function's local variables. JQuery, for example, is wrapped in one giant anon func. This also allows something of a "namespace" such that it can be embedded anywhere and its variables won't clash with the parent's variablesCoimbatore
This is a great solution; the interface could be improved though. github.com/jeresig/jquery.hotkeys has got an ideal one, but does not work with multiple non-modifier keys. I would love to be able type something like $(document).bind('keydown', 'a+b+c', function(){...})Kneel
@AlexanderKachkaev I agree on the interface being sloppy, I wrote this over 3 years ago, when I still made liberal use of global variables and wrote 50-line functions. But I think jQuery should stick to what it's good at: advanced DOM manipulation. Otherwise, raw JS should be used to get the finest degree of control possible. Plus, I rather dislike jQuery's god-object design where $ does everything. What if I only want jQuery's CSS selection functionality and nothing else? Can't do that. You gotta import the entire library because it isn't modular. The pretty interface isn't worth it, IMO.Coimbatore
@AlexanderKachkaev As for user input, these days, I implement input as its own module. Here's a couple examples. 1: Using a "class" (closest thing in ECMAScript 5). 2: Using a "single-state module/namespace" (not sure on the terminology). The Input module appears around line 203. It assumes there's a #canvas, but that could easily be resolved to a parameter passed to pub_init()Coimbatore
NB: keyCode is deprecated - if you switch to key then you get the actual character representation of the key which can be nice.Daphinedaphna
@SeanVieira Huh, that's weird that they would deprecate something that's by now used in thousands of web apps. Either way, I like this key property much better. Thanks for sharing.Coimbatore
Also, for some strange reason, JavaScript arrays allow you to store string indices, like array["foo"] = 5. The array will not be aware that it has "foo", nor will its length be changed, but if you call for array["foo"], it will dutifully return 5. JavaScript never stops finding ways to surprise me with its idiosyncrasies.Coimbatore
That's because arrays in JavaScript are just objects with a special [[Set]] behavior that updates the internal length property and does a few other things. Any key that is not coercible to a number just acts like a normal Object [[Set]] invocation.Daphinedaphna
@SeanVieira Then again, you can do some strange stuff in C as well. For example, did you know that myString[5] is the same as 5[myString], and it won't even give you a compile warning (even with -Wall -pedantic)? It's because the pointer[offset] notation takes the pointer, adds the offset, and then dereferences the result, making myString[5] the same as *(myString + 5).Coimbatore
@SeanVieira Ah, I see what you mean. Object.keys(array) returns ["foo"]. Also, by the way, I updated the answer.Coimbatore
Clearing the map array map = []; surely solves the problem of keyup event not being fired in some cases, but it also induces the problem of not being able to do press multiple times a key while maintaining pressed another key... For example : SHIFT + D and SHIFT + D again without unpressing SHIFT... Do you have any suggestion for solving this ?Mariellamarielle
@Mariellamarielle No built-in way, but you could try clearing all non-modifier keys (i.e. preserving alt, shift and ctrl), or just not clearing the array while doing everything you can to prevent the window focus from being hi-jacked by something else.Coimbatore
@Mariellamarielle @Braden Best. I had met the same problem. And finally I found the reason that the SHIFT is the built-in function of Array . So this code return key[selkey] || key[alias[selkey]]; will return function shift () {[native code]}, which means that the result is always true. How to resolve this? I just change the key from [] to {}. Please pay attention to the position of clearing key. XDPetard
@NiXiaoni Your English is very hard to understand. I don't understand what you were trying to say, and I rejected your edit, because A. **map is not a valid variable name, and your edit would have broken the example code, and B. because an edit is not supposed to read like commentary. That's what the edit summary is for.Coimbatore
@NiXiaoni (and lapin) To address the confusion, I added a new section, detailing a class I wrote for handling keyboard input. Enjoy.Coimbatore
@BradenBest I am very sorry. Please forgive me. I use map just want to highlight it. And YOU can notice where I modified. My meaning is that Array has a method called shift. If you use map = [] and just hit the key e, the test_keys('shift', 'e') will always return true. If you use map={}, you will avoid this mistake(Just like the @lapin's bug). Sorry. I hope you can understand my poor English.Petard
@BradenBest Thank you for your answer. I have learned a lot from you :DPetard
@NiXiaoni I don't think you understand what's going on in the code. Just because arrays have a function named shift doesn't mean an unrelated string mapped to the key code for the shift key is going to somehow block it. Lapin's bug is that if the window loses focus, you have to clear the map variable, otherwise the keyup event never gets picked up by the browser and the values linger. The caveat is that clearing the array will cause an irritating UX bug where the array will clear even though you're still holding down the key. The solution is to avoid calls like alert() that steal focusCoimbatore
Changing the array to an object does nothing. I only changed it because it's better practice to use an object when the values aren't contiguous. (Also, you're welcome)Coimbatore
@BradenBest Oh, I see. Thanks. But my problem is another. I have written some js code to explain my problems. Use map = {} and Use map = []. These are my problems. I hope you can understand. Thanks for your answer.Petard
@NiXiaoni mmm, no. It's because you're using jquery when my code is written to work with raw JavaScript events. Jquery events are the wrong type of event object. Yet another reason why JavaScript needs a stronger type system. Watch, if you use my Input class, you will experience no such bug.Coimbatore
@BradenBest Yeah, your code is running perfectly now :) I have known, thank you. I also hope JavaScript has a stronger type system in the future. I hope...Petard
@BradenBest, Thanks for your answer. Does the helper class you wrote anyhow would solve the problem I have ? (I guess no)Mariellamarielle
@BradenBest, Now, to answer what you suggested, I will definitely try the solution of clearing all non-modifiers. However I am afraid I can't avoid my focus to be randomly stolen. The reason for that is I am using keyboard shortcuts to control youtube embed video through its API, the focus is therefore (I guess) going back and forth my window and the embed video, causing onkeyup not being fired in some cases (which I still don't figured out exactly, maybe when some video buffering/loading is ongoing).Mariellamarielle
@Mariellamarielle from what you're describing, it sounds like JavaScript may very well not be the optimal solution for this. The language (technically, the environment) does have limitations that can't be overcome with simple cleverness. Have you considered a native language like C/C++ (or even Python) with a cross platform library like SDL?Coimbatore
Very helpfull when you try to test if the user want to use the combination of ctrl, shift or alt key with some other keys: you can use if(e.ctrlKey && e.keyCode == 'somekey){ ... } . To test vs ctrl/shift/alt you just check the booleans e.ctrlKey/e.shiftKey/e.altKeyPooley
Couple things - 1. This should be on GitHub so we can all easily help maintain it and use it. 2. There is a bug - for key combos, for ex. if you press down on cmd, down on D, up on D, up on cmd, the keyup event never fires for D (chrome). The fix is if (e.which === 'keyup') { map = {} };Blaseio
@Blaseio are you referring to the helper class? Can gists be used like repos? It would be tedious to make a whole repo for a tiny snippet of code. Sure, I'll make a gist. I'll shoot for tonight. Midnight mountain Time -ishCoimbatore
@BradenBest Perfect answer! Thanks for the clarification.Irritate
I think this only works on <input/> elements? Is this true? Otherwise, I can't get it working when trying to attach the Input() functionality to the body tagRaymonderaymonds
@Raymonderaymonds I'm not sure what you're referring to.. If it's the helper class I wrote, I designed it to work with any element iirc. If you're talking about the general method and having trouble getting an element to focus, make sure you have its tabIndex set properly. Otherwise, you're gonna need to elaborateCoimbatore
You did not mention hardware limitation, for example when I press up and right arrow and the G key at once, all fire keyDown event, but when I press left, up and G, only left and up fires, G is being ignored. I suppose that this vary among all hardware.Eroto
@Eroto what would be the point? There's nothing you can do about that sort of issue in javascript.Coimbatore
C
45
document.onkeydown = keydown; 

function keydown (evt) { 

    if (!evt) evt = event; 

    if (evt.ctrlKey && evt.altKey && evt.keyCode === 115) {

        alert("CTRL+ALT+F4"); 

    } else if (evt.shiftKey && evt.keyCode === 9) { 

        alert("Shift+TAB");

    } 

}
Centaur answered 25/3, 2014 at 19:3 Comment(0)
F
32

You should use the keydown event to keep track of the keys pressed, and you should use the keyup event to keep track of when the keys are released.

See this example: http://jsfiddle.net/vor0nwe/mkHsU/

(Update: I’m reproducing the code here, in case jsfiddle.net bails:) The HTML:

<ul id="log">
    <li>List of keys:</li>
</ul>

...and the Javascript (using jQuery):

var log = $('#log')[0],
    pressedKeys = [];

$(document.body).keydown(function (evt) {
    var li = pressedKeys[evt.keyCode];
    if (!li) {
        li = log.appendChild(document.createElement('li'));
        pressedKeys[evt.keyCode] = li;
    }
    $(li).text('Down: ' + evt.keyCode);
    $(li).removeClass('key-up');
});

$(document.body).keyup(function (evt) {
    var li = pressedKeys[evt.keyCode];
    if (!li) {
       li = log.appendChild(document.createElement('li'));
    }
    $(li).text('Up: ' + evt.keyCode);
    $(li).addClass('key-up');
});

In that example, I’m using an array to keep track of which keys are being pressed. In a real application, you might want to delete each element once their associated key has been released.

Note that while I've used jQuery to make things easy for myself in this example, the concept works just as well when working in 'raw' Javascript.

Folium answered 5/3, 2011 at 11:58 Comment(6)
But as I've thought there's a bug. If you keep pressed one button then switch to another tab(or loose focus) while still holding the button when you refocus on the scrit it will show that the button is pressed even if it's not. :DAmedeo
@Cristy: then you could also add an onblur event handler, which removes all the pressed keys from the array. Once you've lost the focus, it would make sense to have to press all keys again. Unfortunately, there's no JS equivalent to GetKeyboardState.Folium
Thanks, this multiple-keys "mode" solved more bugs then predicted :DAmedeo
Having a problem with Paste on a Mac (Chrome). It successfully gets keydown 91 (command), keydown 86 (v), but then only keyups the 91, leaving 86 down. List of keys: Up: 91, Down: 86. This only seems to happen when letting go of the command key second - if I let go of it first it correctly register the keyup on both.Yodel
It appears that when you press three or more keys at once, it stops detecting any more keys down until you lift one. (Tested with Firefox 22)Stoss
@JamesAlday Same problem. It apparently only affects the Meta (OS) key on Macs. See issue #3 here: bitspushedaround.com/…Absorbing
E
13

for who needs complete example code. Right+Left added

var keyPressed = {};
document.addEventListener('keydown', function(e) {

   keyPressed[e.key + e.location] = true;

    if(keyPressed.Shift1 == true && keyPressed.Control1 == true){
        // Left shift+CONTROL pressed!
        keyPressed = {}; // reset key map
    }
    if(keyPressed.Shift2 == true && keyPressed.Control2 == true){
        // Right shift+CONTROL pressed!
        keyPressed = {};
    }

}, false);

document.addEventListener('keyup', function(e) {
   keyPressed[e.key + e.location] = false;

   keyPressed = {};
}, false);
Eucalyptus answered 30/8, 2017 at 8:10 Comment(1)
This one's the winner for me. Concise, fully functional. If you need to know the name of the key's that you're pressing, just log the keyPressed object at the end of the keydown function. Thanks friend!Rebekahrebekkah
K
9

This is not a universal method, but it's usefull in some cases. It's usefull for combinations like CTRL + something or Shift + something or CTRL + Shift + something, etc.

Example: When you want to print a page using CTRL + P, first key pressed is always CTRL followed by P. Same with CTRL + S, CTRL + U and other combinations.

document.addEventListener('keydown',function(e){
      
    //SHIFT + something
    if(e.shiftKey){
        switch(e.code){

            case 'KeyS':
                console.log('Shift + S');
                break;

        }
    }

    //CTRL + SHIFT + something
    if(e.ctrlKey && e.shiftKey){
        switch(e.code){

            case 'KeyS':
                console.log('CTRL + Shift + S');
                break;

        }
    }

});
Kalie answered 21/9, 2019 at 23:1 Comment(0)
T
8

I used this way (had to check wherever is Shift + Ctrl pressed):

// create some object to save all pressed keys
var keys = {
    shift: false,
    ctrl: false
};

$(document.body).keydown(function(event) {
// save status of the button 'pressed' == 'true'
    if (event.keyCode == 16) {
        keys["shift"] = true;
    } else if (event.keyCode == 17) {
        keys["ctrl"] = true;
    }
    if (keys["shift"] && keys["ctrl"]) {
        $("#convert").trigger("click"); // or do anything else
    }
});

$(document.body).keyup(function(event) {
    // reset status of the button 'released' == 'false'
    if (event.keyCode == 16) {
        keys["shift"] = false;
    } else if (event.keyCode == 17) {
        keys["ctrl"] = false;
    }
});
Tailpipe answered 19/7, 2013 at 15:13 Comment(2)
how would you do this in an angular component?Wit
I don't have experience with angular to answer your question @WitTailpipe
B
4

I like to use this snippet, its very useful for writing game input scripts

var keyMap = [];

window.addEventListener('keydown', (e)=>{
    if(!keyMap.includes(e.keyCode)){
        keyMap.push(e.keyCode);
    }
})

window.addEventListener('keyup', (e)=>{
    if(keyMap.includes(e.keyCode)){
        keyMap.splice(keyMap.indexOf(e.keyCode), 1);
    }
})

function key(x){
    return (keyMap.includes(x));
}

function checkGameKeys(){
    if(key(32)){
        // Space Key
    }
    if(key(37)){
        // Left Arrow Key
    }
    if(key(39)){
        // Right Arrow Key
    }
    if(key(38)){
        // Up Arrow Key
    }
    if(key(40)){
        // Down Arrow Key
    }
    if(key(65)){
        // A Key
    }
    if(key(68)){
        // D Key
    }
    if(key(87)){
        // W Key
    }
    if(key(83)){
        // S Key
    }
}
Buffum answered 26/8, 2020 at 20:49 Comment(3)
Note that Array.includes is not well supported in older browsers or modern IE: caniuse.com/#feat=array-includesAmedeo
Welcome! You should try and present your answer such that it directly answers the question being asked. For example, you should explain why adding the key characters to your keyMap allows for detecting multiple keys. Additionally, in this case, you should explain the purpose of and when checkGameKeys needs to be called.Acute
I had to add checkGameKeys() to just after the if statement in the event handlers for it to work.Jacal
B
4

Here's an implementation of Bradens answer.

var keys = {}
function handleKeyPress(evt) {
  let { keyCode, type } = evt || Event; // to deal with IE
  let isKeyDown = (type == 'keydown');
  keys[keyCode] = isKeyDown;

  // test: enter key is pressed down & shift isn't currently being pressed down 
  if(isKeyDown && keys[13] && !keys[16]){
    console.log('user pressed enter without shift')
  }
};

window.addEventListener("keyup", handleKeyPress);
window.addEventListener("keydown", handleKeyPress);
Benighted answered 18/7, 2021 at 11:23 Comment(0)
P
3

Make the keydown even call multiple functions, with each function checking for a specific key and responding appropriately.

document.keydown = function (key) {

    checkKey("x");
    checkKey("y");
};
Plage answered 9/9, 2015 at 2:18 Comment(0)
N
3
    $(document).ready(function () {
        // using ascii 17 for ctrl, 18 for alt and 83 for "S"
        // ctr+alt+S
        var map = { 17: false, 18: false, 83: false };
        $(document).keyup(function (e) {
            if (e.keyCode in map) {
                map[e.keyCode] = true;
                if (map[17] && map[18] && map[83]) {
                    // Write your own code here, what  you want to do
                    map[17] = false;
                    map[18] = false;
                    map[83] = false;
                }
            }
            else {
                // if u press any other key apart from that "map" will reset.
                map[17] = false;
                map[18] = false;
                map[83] = false;
            }
        });

    });
Nik answered 12/8, 2019 at 13:44 Comment(1)
Thanks for your contribution. please try not to just post code, add some explanation.Adair
B
2

I'd try adding a keypress Event handler upon keydown. E.g:

window.onkeydown = function() {
    // evaluate key and call respective handler
    window.onkeypress = function() {
       // evaluate key and call respective handler
    }
}

window.onkeyup = function() {
    window.onkeypress = void(0) ;
}

This is just meant to illustrate a pattern; I won't go into detail here (especially not into browser specific level2+ Event registration).

Post back please whether this helps or not.

Bibbye answered 5/3, 2011 at 11:35 Comment(3)
This wouldn't work: keypress does not trigger on a lot of keys that keydown and keyup do trigger. Also, not all browsers repeatedly trigger keydown events.Folium
Quirksmode says your wrong: quirksmode.org/dom/events/keys.html . But I won't argue that since I did not test my proposal.Bibbye
Quoted from that page: "When the user presses special keys such as the arrow keys, the browser should NOT fire keypress events". As for the repeats, it lists Opera and Konqueror as not doing that correctly.Folium
S
2

If one of keys pressed is Alt / Crtl / Shift you can use this method:

document.body.addEventListener('keydown', keysDown(actions) );

function actions() {
   // do stuff here
}

// simultaneous pressing Alt + R
function keysDown (cb) {
  return function (zEvent) {
    if (zEvent.altKey &&  zEvent.code === "KeyR" ) {
      return cb()
    }
  }
}
Stilton answered 3/1, 2018 at 16:33 Comment(0)
M
2

If someone needs easy solution.

let keys = [];
document.addEventListener("keydown", (e) => {
  keys.push(e.key);
  if (keys.includes("Control") && keys.includes("o")) {
    console.log("open");
  }

  if (keys.includes("Control") && keys.includes("s")) {
    console.log("save");
  }
});

// clear the keys array
document.addEventListener("keyup", () => {
  keys = [];
});
Moorings answered 5/12, 2022 at 14:12 Comment(1)
Please don't post code-only answers but add a little textual explanation about how and why your approach works and what makes it different from the other answers given. You can find out more at our "How to write a good answer" page.Dyspeptic
D
1
case 65: //A
jp = 1;
setTimeout("jp = 0;", 100);

if(pj > 0) {
ABFunction();
pj = 0;
}
break;

case 66: //B
pj = 1;
setTimeout("pj = 0;", 100);

if(jp > 0) {
ABFunction();
jp = 0;
}
break;

Not the best way, I know.

Deadpan answered 21/1, 2012 at 14:28 Comment(0)
E
1

if you want to find any keypress event with control key you can do like this

onkeypress = (e) =>{
console.log(e);
if(e.ctrlKey && e.code == "KeyZ"){
    document.write("do somthing")
} }
Enclose answered 31/3, 2021 at 18:32 Comment(0)
N
0

Just making something more stable :

var keys = [];
$(document).keydown(function (e) { 
    if(e.which == 32 || e.which == 70){
    keys.push(e.which);
    if(keys.length == 2 && keys.indexOf(32) != -1 && keys.indexOf(70) != -1){
        
        
        
        alert("it WORKS !!"); //MAKE SOMETHING HERE---------------->
        
        
        
        keys.length = 0;
    }else if((keys.indexOf(32) == -1 && keys.indexOf(70) != -1) || (keys.indexOf(32) != -1 && keys.indexOf(70) == -1) && (keys.indexOf(32) > 1 || keys.indexOf(70) > 1)){
    }else{
        keys.length = 0;
    }
}else{
    keys.length = 0;
}
});
Nathalia answered 23/10, 2020 at 21:51 Comment(0)
N
0

For whoever is using React, here is my solution:

import { useEffect, useState } from "react";
import Backdrop from '@mui/material/Backdrop';

export const Example = () => {
  const [backdropOpen, setBackdropOpen] = useState(false);

  useEffect(() => {
    // Keys that need to be pressed at the same time in order for
    // the 'backdropOpen' variable to be 'true'
    const keysArr = ['ControlLeft', 'ShiftLeft', 'AltLeft'];
    const keysMap = {};
    let backdropOpenLocal = false;

    const keydownEvent = 'keydown';
    const keyupEvent = 'keyup';

    const checkKeys = () => {
      const keysArePressed = keysArr.every((value) => keysMap[value] === keydownEvent);
      if (keysArePressed !== backdropOpenLocal) {
        backdropOpenLocal = keysArePressed;
        setBackdropOpen(keysArePressed);
      }
    }

    const handleKeyDown = (event) => {
      const keyCode = event.code;
      if (keysArr.includes(keyCode) && keysMap[keyCode] !== keydownEvent) {
        keysMap[keyCode] = keydownEvent;
      }
      checkKeys();
    }

    const handleKeyUp = (event) => {
      const keyCode = event.code;
      if (keysArr.includes(keyCode) && keysMap[keyCode] !== keyupEvent) {
        keysMap[keyCode] = keyupEvent;
      }
      checkKeys();
    }

    document.addEventListener('keydown', handleKeyDown);
    document.addEventListener('keyup', handleKeyUp);

    return () => {
      document.removeEventListener('keydown', handleKeyDown);
      document.removeEventListener('keyup', handleKeyUp);
    }
  }, []);

  return (
    <React.Fragmemnt>
      <div>
        <Backdrop
          open={backdropOpen}
        >
          <span>
            It worked!
          </span>
        </Backdrop>
      </div>
    </React.Fragmemnt>
  );
}

Keep in mind that we need to use the backdropOpenLocal instead of backdropOpen inside the useEffect function, because we want to update the local scoped variable only and keep the state of the scope.

If we update the state of the Example component and try to access backdropOpen, we will have the same value as before, unless we pass in the backdropOpen in the dependency array of useEffect; this would cause the scoped variables inside useEffect to be reset, and we don't want that.

Novelist answered 20/9, 2022 at 6:52 Comment(0)
H
0

i use cases, ifs and bools. I had a project and this worked great for me


window.addEventListener("keydown", onKeyDown, false);
window.addEventListener("keyup", onKeyUp, false);

function onKeyDown(event) {
  var keyCode = event.keyCode;
  switch (keyCode) {
    case 68: //D
      keyd = true;
      break;
    case 32: //spaaaaaaaaaaaaaaace
      keyspace = true;
      break;
    case 65: //A
      keya = true;
      break;
    case 37:
      keya = true;
      break;
    case 38:
      keyspace = true;
      break;
    case 39:
      keyd = true;
      break;
  }
}

function onKeyUp(event) {
  var keyCode = event.keyCode;

  switch (keyCode) {
    case 68: //dddddd
      keyd = false;
      break;
    case 32: //spaaaaaaaaaaaaaaaaaaaaaace
      keyspace = false;
      break;
    case 65: //aaaaa
      keya = false;
      break;
    case 37:
      keya = false;
      break;
    case 38:
      keyspace = false;
      break;
    case 39:
      keyd = false;
      break;
  }
}

||UPDATE||

I made a library that includes a key variable, here is how to use it:

Key['key']

and here is an example:

function loop(){
    if(Key['w']){
        console.log("w was pressed");
    }
}

setInterval(loop,1);

that's it, you can put it inside of your projects kinda like jquery, this also needs jquery, here it is:

<script src="posh.glitch.me/libraries/library.js"></script>
Handwoven answered 8/2, 2023 at 7:39 Comment(1)
Nice one, though keyCode is being deprecated, maybe you could update the example to use the key attributeMikelmikell
B
0

You Can Add They Keys using keydown and remove them or set them to false on keyup :

var keys = {};
 window.addEventListener("keydown", function(e) {
    if (e.key == 'w') { keys[e.key] = true; }
    if (e.key == 's') { keys[e.key] = true; }
    if (e.key == 'a') { keys[e.key] = true; }
    if (e.key == 'd') { keys[e.key] = true; }
  });
  window.addEventListener("keyup", function(e) {
    if (e.key == 'w') { keys[e.key] = false; }
    if (e.key == 's') { keys[e.key] = false; }
    if (e.key == 'a') { keys[e.key] = false; }
    if (e.key == 'd') { keys[e.key] = false; }
  });

you can check like that:

  if (keys['w'] && keys['d']) {
    console.log("oook");
  }

and when ever you do keyup it will set it to false.

Bedstraw answered 31/1 at 19:17 Comment(0)
H
-2
Easiest, and most Effective Method

//check key press
    function loop(){
        //>>key<< can be any string representing a letter eg: "a", "b", "ctrl",
        if(map[*key*]==true){
         //do something
        }
        //multiple keys
        if(map["x"]==true&&map["ctrl"]==true){
         console.log("x, and ctrl are being held down together")
        }
    }

//>>>variable which will hold all key information<<
    var map={}

//Key Event Listeners
    window.addEventListener("keydown", btnd, true);
    window.addEventListener("keyup", btnu, true);

    //Handle button down
      function btnd(e) {
      map[e.key] = true;
      }

    //Handle Button up
      function btnu(e) {
      map[e.key] = false;
      }

//>>>If you want to see the state of every Key on the Keybaord<<<
    setInterval(() => {
                for (var x in map) {
                    log += "|" + x + "=" + map[x];
                }
                console.log(log);
                log = "";
            }, 300);
Hydrodynamics answered 3/6, 2020 at 3:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.