How to Detect If Multiple Keys Are Pressed At Once Using JavaScript

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

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 :)

How to detect if multiple keys are being pressed at the same time in html using JavaScript

No value can be both 87 and 65 at the same time; this is the reason your if will never trigger. If you press two keys "simultaneously", it will still fire two separate keydown events.

In order to detect 87 and 65 being pressed at the same time, you can do two different approaches:

  • Track last time each key was pressed. If e.g. 87 was pressed, and 65 was pressed a short time ago, trigger your code.

  • On keydown, note that the key is down. On keyup, note that the key is up. React when both 87 and 65 are down.

Detecting multiple key press at once and don't response when other keys are pressed

keyPressListener, is a library that I've developed for that matter.

Firstly, since you want to match only your preferred keys without any other keys, then make sure to set ignoreOtherInput to false:

keyPressListener.config({ignoreOtherInput: false});

Secondly, now you can call the listener function:

keyPressListener.when('A+D+M+N', function(){
window.location.href = '/admin-panel'; // If user pressed (A+D+M+N), go to admin panel
});

So if the user pressed (A+D+M+N) it will redirect him to the admin panel
and if he pressed all the buttons on the keyboard, or (Ctrl+A+D+M+N) for example, it won't response.

More: @https://github.com/RyadPasha/keyPressListener

Detect multiple keys on single keypress event in jQuery

If you want to detect that the d, e, and v keys were all down at the same time, you have to watch both keydown and keyup and keep a map of the ones that are down. When they're all down, fire your event.

For example: Live copy | source

var map = {68: false, 69: false, 86: false};
$(document).keydown(function(e) {
if (e.keyCode in map) {
map[e.keyCode] = true;
if (map[68] && map[69] && map[86]) {
// FIRE EVENT
}
}
}).keyup(function(e) {
if (e.keyCode in map) {
map[e.keyCode] = false;
}
});

I assume you don't care what order they're pressed down in (as that would be a pain to reliably press) as long as they're all down at the same time at some point.

How To Detect Two Characters (Keys) Pressed At The Same TIme

This example uses event listeners to keep track of an object which contains all keys currently being pressed.

We can use this object to check if certain keys are pressed. This means that we can detect as many simultaneous key presses as a user's keyboard will allow.

Importantly it will let you detect when multiple characters are pressed at the same time.

const keysPressed = {};

document.addEventListener('keydown', (event) => {
if (!event.repeat)
keysPressed[event.key] = true;

//You can also include event.repeat as a condition in your cases if you wish an event to only occur once each trigger.
//You can also include Object.keys(keysPressed).length === 2 (for example) if you only wish the case to be valid if just two keys are pressed.
//Finally, instead of event.key === 'x' you can write keysPressed['x']. This would replace both non-default cases in the example. The result is that the event will continue firing if another key is pressed.
switch (true) { //You use true here because we are using logic to determine each case
case event.key === 'x' && keysPressed['z']:
case event.key === 'z' && keysPressed['x']:
console.log('Z and X pressed!');
break;
default:
break;
}
});

document.addEventListener('keyup', (event) => {
if (!event.repeat)
delete keysPressed[event.key];
});

Event listener for multiple keys in React?

Does it work?

useEffect(() => {
document.addEventListener('keydown', (e) => {
e.preventDefault();
if ((e.metaKey || e.ctrlKey) && e.code === 'KeyC') {
console.log('fire!')
}
})
})

How can I catch 2+ key presses at once?

To directly answer your second question.

Here is one way:

var keyPressed = {};

$(window).keydown(function(e) {
keyPressed[e.which] = true;
}).keyup(function(e) {
keyPressed[e.which] = false;
});

Now you can use keyPressed whenever you want to determine if a key is down:

// wherever
var key1 = 65, key2 = 66; // A and B
if (keyPressed[key1] && keyPressed[key2]) {
// A and B are both being pressed.
}


Related Topics



Leave a reply



Submit