Related
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?
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 :)
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");
}
}
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.
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);
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;
}
}
});
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;
}
});
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
}
}
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);
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");
};
$(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;
}
});
});
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.
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()
}
}
}
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.
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")
} }
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;
}
});
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.
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 = [];
});
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;
}
}
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);
I'm writing a simple game in JavaScript, which already handles basic keyboard input like so:
var input = {};
document.onkeydown = function(e) {
input[e.keyCode] = true;
}
document.onkeyup = function(e) {
input[e.keyCode] = false;
}
while (!done) {
handleInput(input);
update();
render();
}
Now I need the game to handle key combos (like CTRL+X for example). I would like it to accept such combos on keyup only.
EDIT: Modifier keys don't need to go all up at the same time as the "main" key. /EDIT
Two possible solutions that come into my mind are:
exposing an array containing a list of keyup events (object with "main" key plus modifiers). The handleInput function would be responsible for draining the queue every time it polls it
keeping track of possible key combos inside handleInput (watching for held down modifier keys) and trigger the combo behavior when the "main" key goes up (I actually don't like this that much)
Would you suggest me an elegant way to extend the current functionality?
Try this ;)
It will appear a message when you press CTRL + X and you release one (or both) of them
var map = {17: false, 88: false};
var fire_event = false;
window.addEventListener('keydown', function(e) {
if (e.keyCode in map) {
map[e.keyCode] = true;
if (map[17] && map[88]) {
fire_event = true;
}
}
}, false);
window.addEventListener('keyup', function(e) {
if (e.keyCode in map) {
map[e.keyCode] = false;
}
if (fire_event) {
fire_event = false;
alert('YOU PRESSED CTRL + X');
}
}, false);
All you have to do is to create upInput variable where you will store your keyup keys in. Then, to check if the combo was pressed you have to check if there is only CTRL + C (or 17, 67) combo in variable and if all of them were true. Then easily print your message and reset upInput variable.
var input = upInput = {};
document.onkeydown = function(e) {
input[e.keyCode] = true;
if (JSON.stringify(Object.keys(upInput)) != '["17","67"]')
upInput = {};
}
document.onkeyup = function(e) {
var k = e.keyCode;
input[k] = false;
if ([17, 67].indexOf(k) > -1)
upInput[k] = true;
if (JSON.stringify(Object.keys(upInput)) == '["17","67"]'
&& upInput[17] && upInput[67]) // CTRL + C
alert('YES!'),
upInput = {};
}
Demo
Hope that helps!
I would do in one of those ways:
Checking for the combinations in the handleInput part (kind of your
second apporach)
Keeping a list of the very last X keyboard events (where X is max
length of combo sequence) and checking whether this list matches to
any of the predefined combo triggers
Try if it works:
var KEY_QUEUE = [];
var KEYs = [17, 88];
document.onkeydown = function (e) {
if (KEY_QUEUE.indexOf(e.keyCode) === -1) {
KEY_QUEUE.push(e.keyCode);
}
};
document.onkeyup = function (e) {
var index = KEY_QUEUE.indexOf(e.keyCode);
if ( isSubArray(KEYs, KEY_QUEUE)
&& KEYs.indexOf(e.keyCode) > -1 ) {
console.log("You press CTRL + X");
}
KEY_QUEUE.splice(index, 1);
};
function isSubArray(ary, targetAry) {
return new RegExp(ary.toString()).test(targetAry.toString());
}
I've checked some cases like Ctrl x, x Ctrl, Ctrl x a, Ctrl a x, and it works as I expected. But I am not sure if it is the same with you idea.
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?
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 :)
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");
}
}
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.
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);
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;
}
}
});
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;
}
});
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
}
}
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);
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");
};
$(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;
}
});
});
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.
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()
}
}
}
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.
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")
} }
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;
}
});
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.
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 = [];
});
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;
}
}
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);
Is there a way for jQuery to detect that more than one key was pressed at the same time?
Is there any alternative that allows for pressing two keys at the same time to be detected?
In order to detect multiple keys being held down, use the keydown and keyup events.
var keys = {};
$(document).keydown(function (e) {
keys[e.which] = true;
});
$(document).keyup(function (e) {
delete keys[e.which];
});
I've put together a demo here: http://jsfiddle.net/gFcuU/. It's kind of fun, though I noticed my keyboard is only able to detect at most 6 keys.
It depends. For "normal" keys, that means Non- Shift, Ctrl, ALT, (CMD), the answer is no, the event handler will catch/fire in a queue, one after another.
For the modifier keys I mentioned above, there is a property on the event object.
Example:
$(document).bind('keypress', function(event) {
if( event.which === 65 && event.shiftKey ) {
alert('you pressed SHIFT+A');
}
});
Jsfiddle demo.
Other propertys are:
event.ctrlKey
event.altKey
event.metaKey
If you just want to fire a handler when several keys are pressed in series, try something like:
jQuery.multipress = function (keys, handler) {
'use strict';
if (keys.length === 0) {
return;
}
var down = {};
jQuery(document).keydown(function (event) {
down[event.keyCode] = true;
}).keyup(function (event) {
// Copy keys array, build array of pressed keys
var remaining = keys.slice(0),
pressed = Object.keys(down).map(function (num) { return parseInt(num, 10); }),
indexOfKey;
// Remove pressedKeys from remainingKeys
jQuery.each(pressed, function (i, key) {
if (down[key] === true) {
down[key] = false;
indexOfKey = remaining.indexOf(key);
if (indexOfKey > -1) {
remaining.splice(indexOfKey, 1);
}
}
});
// If we hit all the keys, fire off handler
if (remaining.length === 0) {
handler(event);
}
});
};
For instance, to fire on s-t,
jQuery.multipress([83, 84], function () { alert('You pressed s-t'); })
Nope. keypress will fire for every individual key that is pressed - except for modifier keys such as CTRL, ALT and SHIFT, you can combine them with other keys, so long as it is only one other key.
Here's a jQuery solution based on
Maciej's answer https://stackoverflow.com/a/21522329/
// the array to add pressed keys to
var keys = [];
// listen for which key is pressed
document.addEventListener('keydown', (event) => {
if ($.inArray(event.keyCode, keys) == -1) {
keys.push(event.keyCode);
}
console.log('keys array after pressed = ' + keys);
});
// listen for which key is unpressed
document.addEventListener('keyup', (event) => {
// the key to remove
var removeKey = event.keyCode;
// remove it
keys = $.grep(keys, function(value) {
return value != removeKey;
});
console.log('keys array after unpress = ' + keys);
});
// assign key number to a recognizable value name
var w = 87;
var d = 68;
var s = 83;
var a = 65;
// determine which keys are pressed
document.addEventListener('keydown', (event) => {
if ($.inArray(w, keys) != -1 && $.inArray(d, keys) != -1) { // w + d
console.log('function for w + d combo');
} else if ($.inArray(s, keys) != -1 && $.inArray(a, keys) != -1) { // s + a
console.log('function for s + a combo');
}
})
fiddle demo
https://jsfiddle.net/Hastig/us00zdo6/
If you're using esma6, you could do the following using sets.
const KEYS = new Set();
$(document).keydown(function (e) {
KEYS.add(e.which);
if(KEYS.has(12) && KEYS.has(31)){
//do something
}
});
$(document).keyup(function (e) {
KEYS.delete(e.which);
});
And if you want the user to press them together, you can do:
const KEYS = new Set(); // for other purposes
const RECENT_KEYS = new Set(); // the recently pressed keys
const KEY_TIMELAPSE = 100 // the miliseconds of difference between keys
$(document).keydown(function (e) {
KEYS.add(e.which);
RECENT_KEYS.add(e.which);
setTimeout(()=>{
RECENT_KEYS.delete(e.which);
}, KEY_TIMELAPSE);
if(RECENT_KEYS.has(37) && RECENT_KEYS.has(38)){
// Do something
}
});
$(document).keyup(function (e) {
KEYS.delete(e.which);
RECENT_KEYS.delete(e.which);
});
Here is a codepen https://codepen.io/Programador-Anonimo/pen/NoEeKM?editors=0010
As my gist expired ( no one was using it :( ) I decided to update the answer with more 2017 solution. Check below.
You can use my plugin for jquery to detect shortcuts.
It basically cache's events and get what keys are pressed at the moment. If all the keys are pressed it fires function.
https://github.com/maciekpaprocki/bindShortcut (expired!)
You have small explanation how to use it in readme file. Hope this helps. Feedback more than appreciated.
Edit 2017:
It's 2017 and we don't need jQuery plugins to solve stuff like that. In short you will need something like this:
let pressed = {};
document.addEventListener('keydown', (event) => {
pressed[event.key] = true;
});
document.addEventListener('keyup', (event) => {
delete pressed[event.key];
});
//and now write your code
document.addEventListener('keydown', (event) => {
if(pressed[firstKey]&&pressed[secondKey]){
//dosomething
}
});
Older browsers might have some quirks, however from IE9 everything should work fine except of marginal amounts of OSs that don't support right event delegation (super old ubuntu etc.). There's no way to fix that in them as that's not the browser issue.
There are some quirks in new macs connected to boolean keys like for example caps lock.
Read more: https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent#Key_names_and_Char_values
According to #David Tang's solution, here is a quick and dirty customization for capturing Shift+Ctrl+A combination:
var pressedKeys = {};
function checkPressedKeys() {
var shiftPressed=false, ctrlPressed=false, aPressed=false;
for (var i in pressedKeys) {
if (!pressedKeys.hasOwnProperty(i)) continue;
if(i==16){
shiftPressed=true;
}
else if(i==17){
ctrlPressed=true;
}
else if(i==65){
aPressed=true;
}
}
if(shiftPressed && ctrlPressed && aPressed){
//do whatever you want here.
}
}
$(document).ready(function(){
$(document).keydown(function (e) {
pressedKeys[e.which] = true;
checkPressedKeys();
});
$(document).keyup(function (e) {
delete pressedKeys[e.which];
});
});
Here is my code:
function pauseSound() {
var pauseSound = document.getElementById("backgroundMusic");
pauseSound.pause();
}
I would like to add a keyboard shortcut to this code, how can I do this so that the function can also be executed when a button is clicked too?
Tried to add an else if statement but it doesn't work, any ideas?
function doc_keyUp(e) {
if (e.ctrlKey && e.keyCode == 88) {
pauseSound();
}
else if (e.ctrlKey && e.keyCode == 84) {
playSound();
}
}
An event handler for the document's keyup event seems like an appropriate solution.
Note: KeyboardEvent.keyCode was deprecated in favor of KeyboardEvent.key.
// define a handler
function doc_keyUp(e) {
// this would test for whichever key is 40 (down arrow) and the ctrl key at the same time
if (e.ctrlKey && e.key === 'ArrowDown') {
// call your function to do the thing
pauseSound();
}
}
// register the handler
document.addEventListener('keyup', doc_keyUp, false);
If you want to trigger an event after pressing a key, try:
In this example press ALT+a:
document.onkeyup = function () {
var e = e || window.event; // for IE to cover IEs window event-object
if(e.altKey && e.which == 65) {
alert('Keyboard shortcut working!');
return false;
}
}
Here is a fiddle: https://jsfiddle.net/dmtf6n27/38/
Please also note there is a difference for the keycode numbers, whether you are using onkeypress or onkeyup. W3 Schools' "KeyboardEvent keyCode" Property has more information.
//For single key: Short cut key for 'Z'
document.onkeypress = function (e) {
var evt = window.event || e;
switch (evt.keyCode) {
case 90:
// Call your method Here
break;
}
}
//For combine keys like Alt+P
document.onkeyup = function (e) {
var evt = window.event || e;
if (evt.keyCode == 80 && evt.altKey) {
// Call Your method here
}
}
}
//ensure if short cut keys are case sensitive.
// If its not case sensitive then
//check with the evt.keyCode values for both upper case and lower case. ......
Here's my solution:
HTMLElement.prototype.onshortcut = function(shortcut, handler) {
var currentKeys = []
function reset() {
currentKeys = []
}
function shortcutMatches() {
currentKeys.sort()
shortcut.sort()
return (
JSON.stringify(currentKeys) ==
JSON.stringify(shortcut)
)
}
this.onkeydown = function(ev) {
currentKeys.push(ev.key)
if (shortcutMatches()) {
ev.preventDefault()
reset()
handler(this)
}
}
this.onkeyup = reset
}
document.body.onshortcut(["Control", "Shift", "P"], el => {
alert("Hello!")
})
When you call my function, it will create an array called currentKeys; these are the keys that will are being held down at that moment.
Every time a key is pressed, sensed because of onkeydown, it is added to the currentKeys array.
When the keys are released, sensed because of onkeyup, the array is reset meaning that no keys are being pressed at that moment.
Each time it will check if the shortcut matches. If it does it will call the handler.
This worked for me
document.onkeyup=function(e){
var e = e || window.event;
if(e.which == 37) {
$("#prev").click()
}else if(e.which == 39){
$("#next").click()
}
}
Catch the key code and then call your function. This example catches the ESC key and calls your function:
function getKey(key) {
if ( key == null ) {
keycode = event.keyCode;
// To Mozilla
} else {
keycode = key.keyCode;
}
// Return the key in lower case form
if (keycode ==27){
//alert(keycode);
pauseSound();
return false;
}
//return String.fromCharCode(keycode).toLowerCase();
}
$(document).ready( function (){
$(document).keydown(function (eventObj){
//alert("Keydown: The key is: "+getKey(eventObj));
getKey(eventObj);
});
});
You'll need JQUERY for this example.
These appear to all be using the deprecated keyCode and which properties. Here is a non-deprecated version using jQuery to wire up the event:
$("body").on("keyup", function (e) {
if(e.ctrlKey && e.key == 'x')
pauseSound();
else if(e.ctrlKey && e.key == 't')
playSound();
})
Note: Ctrl+t may already be assigned to opening a new browser tab.
Here's some stuff to use if you want. You can register a bunch of keys and handler with it.
Comments are in the code, but in short it sets up a listener on the document and manages a hash with the key combinations for which you want to listen.
When you register a key (combination) to listen for, you submit the keycode (preferrably as a constant taken from the exported "key" property, to which you can add more constants for yourself), a handler function and possibly an options hash where you say if the Ctrl and/or Alt key are involved in your plans for this key.
When you de-register a key (combination) you just submit the key and the optional hash for Ctrl/Alt-ness.
window.npup = (function keypressListener() {
// Object to hold keyCode/handler mappings
var mappings = {};
// Default options for additional meta keys
var defaultOptions = {ctrl:false, alt:false};
// Flag for if we're running checks or not
var active = false;
// The function that gets called on keyup.
// Tries to find a handler to execute
function driver(event) {
var keyCode = event.keyCode, ctrl = !!event.ctrlKey, alt = !!event.altKey;
var key = buildKey(keyCode, ctrl, alt);
var handler = mappings[key];
if (handler) {handler(event);}
}
// Take the three props and make a string to use as key in the hash
function buildKey(keyCode, ctrl, alt) {return (keyCode+'_'+ctrl+'_'+alt);}
function listen(keyCode, handler, options) {
// Build default options if there are none submitted
options = options || defaultOptions;
if (typeof handler!=='function') {throw new Error('Submit a handler for keyCode #'+keyCode+'(ctrl:'+!!options.ctrl+', alt:'+options.alt+')');}
// Build a key and map handler for the key combination
var key = buildKey(keyCode, !!options.ctrl, !!options.alt);
mappings[key] = handler;
}
function unListen(keyCode, options) {
// Build default options if there are none submitted
options = options || defaultOptions;
// Build a key and map handler for the key combination
var key = buildKey(keyCode, !!options.ctrl, !!options.alt);
// Delete what was found
delete mappings[key];
}
// Rudimentary attempt att cross-browser-ness
var xb = {
addEventListener: function (element, eventName, handler) {
if (element.attachEvent) {element.attachEvent('on'+eventName, handler);}
else {element.addEventListener(eventName, handler, false);}
}
, removeEventListener: function (element, eventName, handler) {
if (element.attachEvent) {element.detachEvent('on'+eventName, handler);}
else {element.removeEventListener(eventName, handler, false);}
}
};
function setActive(activate) {
activate = (typeof activate==='undefined' || !!activate); // true is default
if (activate===active) {return;} // already in the desired state, do nothing
var addOrRemove = activate ? 'addEventListener' : 'removeEventListener';
xb[addOrRemove](document, 'keyup', driver);
active = activate;
}
// Activate on load
setActive();
// export API
return {
// Add/replace handler for a keycode.
// Submit keycode, handler function and an optional hash with booleans for properties 'ctrl' and 'alt'
listen: listen
// Remove handler for a keycode
// Submit keycode and an optional hash with booleans for properties 'ctrl' and 'alt'
, unListen: unListen
// Turn on or off the whole thing.
// Submit a boolean. No arg means true
, setActive: setActive
// Keycode constants, fill in your own here
, key : {
VK_F1 : 112
, VK_F2: 113
, VK_A: 65
, VK_B: 66
, VK_C: 67
}
};
})();
// Small demo of listen and unListen
// Usage:
// listen(key, handler [,options])
// unListen(key, [,options])
npup.listen(npup.key.VK_F1, function (event) {
console.log('F1, adding listener on \'B\'');
npup.listen(npup.key.VK_B, function (event) {
console.log('B');
});
});
npup.listen(npup.key.VK_F2, function (event) {
console.log('F2, removing listener on \'B\'');
npup.unListen(npup.key.VK_B);
});
npup.listen(npup.key.VK_A, function (event) {
console.log('ctrl-A');
}, {ctrl: true});
npup.listen(npup.key.VK_A, function (event) {
console.log('ctrl-alt-A');
}, {ctrl: true, alt: true});
npup.listen(npup.key.VK_C, function (event) {
console.log('ctrl-alt-C => It all ends!');
npup.setActive(false);
}, {ctrl: true, alt: true});
It is not terribly tested, but seemed to work OK.
Look at Javascript Char Codes (Key Codes) to find a lot of keyCodes to use,
Solution:
var activeKeys = [];
//determine operating system
var os = false;
window.addEventListener('load', function() {
var userAgent = navigator.appVersion;
if (userAgent.indexOf("Win") != -1) os = "windows";
if (userAgent.indexOf("Mac") != -1) os = "osx";
if (userAgent.indexOf("X11") != -1) os = "unix";
if (userAgent.indexOf("Linux") != -1) os = "linux";
});
window.addEventListener('keydown', function(e) {
if (activeKeys.indexOf(e.which) == -1) {
activeKeys.push(e.which);
}
if (os == 'osx') {
} else {
//use indexOf function to check for keys being pressed IE
if (activeKeys.indexOf(17) != -1 && activeKeys.indexOf(86) != -1) {
console.log('you are trying to paste with control+v keys');
}
/*
the control and v keys (for paste)
if(activeKeys.indexOf(17) != -1 && activeKeys.indexOf(86) != -1){
command and v keys are being pressed
}
*/
}
});
window.addEventListener('keyup', function(e) {
var result = activeKeys.indexOf(e.which);
if (result != -1) {
activeKeys.splice(result, 1);
}
});
Explanation:
I ran into this same problem and came up with my own solution. e.metaKey didn't seem to work with the keyup event in Chrome and Safari. However, I'm not sure if it was specific to my application since I had other algorithms blocking some key events and I may have mistakenly blocked the meta key.
This algorithm monitors for keys going down and then adds them to a list of keys that are currently being pressed. When released, the key is removed from the list. Check for simultaneous keys in the list by using indexOf to find key codes in the array.
Saving with ctrl+s in React
useEffect(() => {
document.onkeydown = function (e) {
if (e.ctrlKey == true && e.key == 's') {
e.preventDefault() // to override browser's default save page feature
alert('ctrl+s is working for save!') // invoke your API to save
}
}
}, [])
Many of these answers suggest forcibly overriding document.onkeypress. This is not a good practice because it only allows for a single event handler to be assigned. If any other handlers were previously set up by another script they will be replaced by your function. If you assign another handler later, it will replace the one you assigned here.
A much better approach is to use addEventListener to attach your keyboard shortcut. This allows you to attach as many handlers as necessary and will not interfere with any external libraries that may have attached their own.
Additionally, the UIEvent.which property was never standardized and should not be used. The same goes for KeyboardEvent.keyCode. The current standards compliant property you should use to check which key was pressed is KeyboardEvent.key. Find the key you want in the full list of available values.
For best performance, return early if your desired modifier key is not pressed. As well, rather than having multiple keypress event listeners, use a single one with a swtich/case statement to react appropriately to each key that you want to handle.
Also, do not forget to cancel the default behavior of the key with Event.preventDefault if necessary. Though, there are some shortcuts that you cannot override like ctrl+w.
document.addEventListener('keypress', event => {
if (!event.ctrlKey) { return; }
event.preventDefault();
switch (event.key) {
case 'x' : doSomething(); break
case 'z' : doSomethingElse(); break;
default : console.log('unhandled key was pressed');
}
});