I have the following problem:
I'm trying to write a Javascript game, and the character is being controlled by the arrow keys.
The problem is, when one keeps the key pressed, there is a short delay between firing the first keypress and the repeated keypress.
Also, when one presses the "right arrow key" and keeps it pressed, and then presses the "up arrow key" the character doesn't move to the top right corner, but stops the moving in the right direction and starts moving up.
This is the code I'm using:
<body onLoad="Load()" onKeyDown="Pressed(event)">
function Pressed(e) {
cxc = e.keyCode;
if(cxc == 37)
Move(-1,0);
if(cxc == 38)
Move(0,-1);
if(cxc == 39)
Move(1,0);
if(cxc == 40)
Move(0,1);
}
Does anybody have an idea?
If you want key repeat in a controllable fashion, you will have to implement it yourself, as keypress events are fired dependent on the OS's idea of how keys should repeat. That means there may be variable initial and following delays, and holding down two keys at once will cause only one of them to repeat.
You will have to keep a record of whether each key is currently pressed, and ignore keydown events when the key is already down. This is because many browsers will fire a keydown as well as a keypress event when an autorepeat occurs, and if you're reproducing key repeat yourself you'll need to suppress that.
For example:
// Keyboard input with customisable repeat (set to 0 for no key repeat)
//
function KeyboardController(keys, repeat) {
// Lookup of key codes to timer ID, or null for no repeat
//
var timers= {};
// When key is pressed and we don't already think it's pressed, call the
// key action callback and set a timer to generate another one after a delay
//
document.onkeydown= function(event) {
var key= (event || window.event).keyCode;
if (!(key in keys))
return true;
if (!(key in timers)) {
timers[key]= null;
keys[key]();
if (repeat!==0)
timers[key]= setInterval(keys[key], repeat);
}
return false;
};
// Cancel timeout and mark key as released on keyup
//
document.onkeyup= function(event) {
var key= (event || window.event).keyCode;
if (key in timers) {
if (timers[key]!==null)
clearInterval(timers[key]);
delete timers[key];
}
};
// When window is unfocused we may not get key events. To prevent this
// causing a key to 'get stuck down', cancel all held keys
//
window.onblur= function() {
for (key in timers)
if (timers[key]!==null)
clearInterval(timers[key]);
timers= {};
};
};
then:
// Arrow key movement. Repeat key five times a second
//
KeyboardController({
37: function() { Move(-1, 0); },
38: function() { Move(0, -1); },
39: function() { Move(1, 0); },
40: function() { Move(0, 1); }
}, 200);
Although, most action-based games have a fixed-time main frame loop, which you can tie the key up/down handling into.
I've solved it like this:
var pressedl = 0;
var pressedu = 0;
var pressedr = 0;
var pressedd = 0;
function Down(e) {
cxc = e.keyCode;
if(cxc == 37)
pressedl = 1;
if(cxc == 38)
pressedu = 1;
if(cxc == 39)
pressedr = 1;
if(cxc == 40)
pressedd = 1;
//alert(cxc);
}
function Up(e) {
cxc = e.keyCode;
if(cxc == 37)
pressedl = 0;
if(cxc == 38)
pressedu = 0;
if(cxc == 39)
pressedr = 0;
if(cxc == 40)
pressedd = 0;
//alert(cxc);
}
<body onLoad="Load()" onKeyDown="Down(event)" onKeyUp="Up(event)">
You could start the movement onkeydown and only end it onkeyup?
This is nearly the same as the excellent answer from #bobince
I've amended it slightly to allow individual values for the interval
// Keyboard input with customisable repeat (set to 0 for no key repeat)
// usage
/**
KeyboardController({
32: {interval:0, callback: startGame },
37: {interval:10, callback: function() { padSpeed -= 5; } },
39: {interval:10, callback: function() { padSpeed += 5; } }
});
*/
function KeyboardController(keyset) {
// Lookup of key codes to timer ID, or null for no repeat
//
var timers= {};
// When key is pressed and we don't already think it's pressed, call the
// key action callback and set a timer to generate another one after a delay
//
document.onkeydown= function(event) {
var key= (event || window.event).keyCode;
if (!(key in keyset))
return true;
if (!(key in timers)) {
timers[key]= null;
keyset[key].callback();
if (keyset[key].interval !== 0)
timers[key]= setInterval(keyset[key].callback, keyset[key].interval);
}
return false;
};
// Cancel timeout and mark key as released on keyup
//
document.onkeyup= function(event) {
var key= (event || window.event).keyCode;
if (key in timers) {
if (timers[key]!==null)
clearInterval(timers[key]);
delete timers[key];
}
};
// When window is unfocused we may not get key events. To prevent this
// causing a key to 'get stuck down', cancel all held keys
//
window.onblur= function() {
for (key in timers)
if (timers[key]!==null)
clearInterval(timers[key]);
timers= {};
};
};
I've also got a plan to use setTimeout instead of setInterval for the reasons given in this question: setTimeout or setInterval?
I'll update this answer once I've amended and tested.
The typical solution to triggering actions in a game is to add a layer of indirection: don't let the user's action update entity state until the game loop runs on the next frame. (Yes, this applies to mouse events and just about anything else that affects game state as well in most cases)
It might make intuitive sense to trigger a game event as soon as a key is pressed; after all, that's how you'd normally respond to an event: right away in the listener callback.
However, in games and animations, the update/rendering loop is the only place where entity updates such as movement should occur. Messing with positions outside of the rendering loop bypasses the normal flow illustrated below:
[initialize state]
|
v
.-----------------.
| synchronously |
| update/render |
| a single frame |
`-----------------`
^ |
| v
(time passes asynchronously,
events fire between frames)
When events fire, they should modify intermediate state that the update code can then take into consideration when it comes time to update entity positions and states.
Specifically, you could use flags that represent which keys were pressed, flip them on whenever a keydown event fires, and flip them off whenever a keyup event fires. Then, the key will be processed regardless of any operating system delay buffering in the update loop.
Rather than a boolean for every key, simply add the keys to a set when pressed and remove them when they're released.
Here's a minimal example:
const keysPressed = new Set();
document.addEventListener("keydown", e => {
keysPressed.add(e.code);
});
document.addEventListener("keyup", e => {
keysPressed.delete(e.code);
});
(function update() {
requestAnimationFrame(update);
document.body.innerHTML = `
<p>These keys are pressed:</p>
<ul>
${[...keysPressed]
.map(e => `<li>${e}</li>`)
.join("")
}
</ul>
`;
})();
The above code works as a drop-in to implement rudimentary game movement, with some default prevention as needed:
const keysPressed = new Set();
const preventedKeys = new Set([
"ArrowUp",
"ArrowDown",
"ArrowLeft",
"ArrowRight",
]);
document.addEventListener("keydown", e => {
if (preventedKeys.has(e.code)) {
e.preventDefault();
}
keysPressed.add(e.code);
});
document.addEventListener("keyup", e => {
keysPressed.delete(e.code);
});
const player = {
x: 0,
y: 0,
speed: 2,
el: document.querySelector("#player"),
render() {
this.el.style.left = player.x + "px";
this.el.style.top = player.y + "px";
},
actions: {
ArrowLeft() { this.x -= this.speed; },
ArrowDown() { this.y += this.speed; },
ArrowUp() { this.y -= this.speed; },
ArrowRight() { this.x += this.speed; },
},
update(keysPressed) {
Object.entries(this.actions)
.forEach(([key, action]) =>
keysPressed.has(key) && action.call(this)
)
;
},
};
(function update() {
requestAnimationFrame(update);
player.update(keysPressed);
player.render();
})();
.wrapper {
position: relative;
}
#player {
width: 40px;
height: 40px;
background: purple;
position: absolute;
}
<p>Use arrow keys to move</p>
<div class="wrapper">
<div id="player"></div>
</div>
If you want some sort of cooldown/retrigger period (for example, the player is holding down a "fire" key but a gun should not fire a new bullet on every frame), I suggest handling that in each entity rather than in the logic for the above key-handling code. The key logic is responsible for identifying which keys are being pressed and nothing else.
Note that keyboards have limits to how many keys they register as pressed at once.
setTimeout and setInterval are imprecise. Rely on requestAnimationFrame as much as possible for games and animations. You can use a tick counter to determine elapsed time such that all entities in the game are synchronized to the same clock. Of course, much is application-dependent.
See also:
Moving a triangular ship in a canvas game in plain JS
Smooth character movement in canvas game using keyboard controls
How to use arrow keys to move object smoothly in canvas
As this event is to move whatever from one position to one position, why don't you use onkeypress event, so in that way if the user key pressed the up key, the whatever will keep moving up, as the Pressed(e) will be called many times until the user releases the key.
<body onLoad="Load()" onkeypress="Pressed(event)">
This here is Lucas' solution in a more abstract version:
http://jsfiddle.net/V2JeN/4/
Seems you can only press 3 keys at a time to my surprise.
I'm a total novice at this, but why not combine KeyDown with KeyUp? I am working on a similar project right now and, after checking out quirksmode I am going to set forth at figuring out how to combine the two events such that the whole time between a Down and Up realizes the desired affect.
Modernized #bobince's great answer for my project as follows...
// Keyboard.js
'use strict'
class KeyboardController {
constructor(keys, repeat) {
this.keys = keys;
this.repeat = repeat;
this.timers = {};
document.onkeydown = event => this.keydown(event);
document.onkeyup = event => this.keyup(event);
window.onblur = () => this.blur;
}
keydown(event) {
event.stopPropagation();
const code = event.code;
if (!(code in this.keys)) return true;
if (!(code in this.timers)) {
this.timers[code] = null;
this.keys[code]();
if (this.repeat) this.timers[code] = setInterval(this.keys[code], this.repeat);
}
return false;
}
keyup(event) {
const code = event.code;
if (code in this.timers) {
if (this.timers[code]) clearInterval(this.timers[code]);
delete this.timers[code];
}
}
blur() {
for (let key in this.timers)
if (this.timers[key]) clearInterval(this.timers[key]);
this.timers = {};
}
}
Same caller pattern...
const keyboard = new KeyboardController({
ArrowLeft: () => {/* your logic here */},
ArrowRight: () => {},
ArrowUp: () => {},
ArrowDown: () => {}
}, 65);
Related
I am using WordPress jQuery to build some shortcuts for my site.
It works as intended, however it doesn't stop when other keys are pressed.
For example, if I press, f it performs the necessary task. But if I press CTRL + f then also it does the task.
Infact, I tried, other keys like f and p and that too worked.
I want it to only work on the specific key. Incase of any other key press, it should not run. It should run on clicking f, but NOT run on CTRL + f.
I tested this on Chrome 92.0.4515.159 and Chrome Beta Version 94.0.4606.31 on Windows 7 and 8.
jQuery(window).keydown(function(e) {
if( e.which === 27){
jQuery('.SearchCreationsClose').click();
}
if(e.key == "f"){
e.preventDefault();
jQuery('.SearchCreationsIcon').click();
}
});
I saw the answer from #diego and understand that you want to implement shortcuts such that they are limited to a specific key combination, extra keypress will stop the function.
The solution was really good, but reading comments there are 2 main problems, 1.) It stops when the window goes out of focus, and 2.) event.preventDefault doesn't not work
I have a solution, that works without the timeout function, making it solve the e.preventDefault problem. And I use a different approach to make it happen.
var MyKeys= 0;
jQuery(window).focus(function(){
MyKeys = 0;
});
jQuery(window).keyup(function(e){
if(MyKeys > 0){
MyKeys--
}
else {
MyKeys = 0;
}
});
jQuery(window).keydown(function(e){
if (!e.repeat) {
MyKeys++
}
if(e.which == 70 && MyKeys === 1){
e.preventDefault();
console.log('f is pressed')
}
if(e.which == 70 && (e.ctrlKey || e.metaKey) && MyKeys === 2){
e.preventDefault();
console.log('ctrl+f is pressed')
}
});
Pretty sure that it solves the prevent default problem..
And I think it will solve the Alt + Tab problem too... (Haven't tested but am confident)
It isn't perfect. There is only a small limitation, if a user comes focuses into your Windows with a key already pressed, he would have to lift the key before being able to use the shortcut. He cants just focus in with CTRL pressed and then press f.
I think the best solution would be to merge this: Can jQuery .keypress() detect more than one key at the same time? with some sort of timeout like this:
var keys = {};
var eventTimeout;
$(document).keydown(function (e) {
// Reset timeout
if (eventTimeout) {
clearTimeout(eventTimeout);
}
keys[e.which] = true;
eventTimeout = setTimeout(function () {
if (typeof keys[70] !== "undefined" && Object.keys(keys).length === 1) { // 70 is the "f" key and it is the only pressed key
e.preventDefault();
$('.SearchCreationsIcon').click();
}
}, 200); // Takes 200ms of time before going on
});
$(document).keyup(function (e) {
setTimeout(function(){
delete keys[e.which];
},210);
});
The idea is that when you detect keydown you wait a little bit to see if there are others key pressed or just the "f" key. The keyup clears the buffer to ensure there are no leftovers
Edit for combo:
This would be the solution to catch CTRL + SHIFT + S, see updated code. I just moved out code from if and nested into separated function. You could easily abstract the function to accept an arbitrary number of keys simultaneously pressed together with which keys should be pressed and create a single function. You got the idea
var keys = {};
var eventTimeout;
$(document).keydown(function (e) {
// Reset timeout
if (eventTimeout) {
clearTimeout(eventTimeout);
}
keys[e.which] = true;
eventTimeout = setTimeout(function () {
if (isFPressed() || isMyComboPressed()) { // 70 is the "f" key and it is the only pressed key
e.preventDefault();
$('.SearchCreationsIcon').click();
}
}, 200); // Takes 200ms of time before going on
});
function isFPressed() {
return typeof keys[70] !== "undefined" && Object.keys(keys).length === 1;
}
/**
* 16 - Key code of SHIFT
* 17 - Key code of CTRL
* 83 - Key code of "s"
* #returns {boolean}
*/
function isMyComboPressed(){
return typeof keys[16] !== "undefined" && keys[17] !== "undefined" && keys[83] !== "undefined" && Object.keys(keys).length === 3;
}
$(document).keyup(function (e) {
setTimeout(function () {
delete keys[e.which];
}, 210);
});
Use e.ctrlKey which is true if ctrl key was pressed:
jQuery(window).keydown(function(e) {
if(e.key=='f' && e.ctrlKey){
console.log('ctrl+f');}
else if (e.key=='f'){
console.log('"JUST" f key');
}
});
If you want to catch only f keydown but not ctrl+f:
jQuery(window).keydown(function(e) {
if(e.key=='f' && !e.ctrlKey){
console.log('"JUST" f key');
}
});
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);
It is easy to detect control key event with
document.addEventListener('keyup', handler, false);
...
function handler(e) {
var key = e.which || e.keyCode;
if (key == 17) { // Control key
...
}
}
The problem is that any key combinations such as control-c and control-v will also trigger the event, and it seems that the event handler cannot distinguish between a single control and the control within a control-c.
What I want is to allow only a single key press-and-release, but not a key combination, to trigger the event.
How about this:
var controlConsidered = false;
function ctrlPressed() {
console.log("Control was pressed.");
}
function keydownhandler(e) {
var key = e.which || e.keyCode;
controlConsidered = (key == 17);
}
function keyuphandler(e) {
var key = e.which || e.keyCode;
if (key == 17 && controlConsidered) {
ctrlPressed();
}
}
document.addEventListener('keydown', keydownhandler, false);
document.addEventListener('keyup', keyuphandler, false);
You can actually use e.ctrlKey and e.altKey, and the value will be true or false based on whether or not those keys are pressed.
In your event, it would be something like:
if (e.ctrlKey || e.altKey) {
return;
}
EDIT
For reference, you can view https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/ctrlKey.
This property is actually a boolean that returns whether or not the ctrl key was pressed when the event was fired.
Then, you can see that if the user decides to press the ctrl key and no other key, this method will not return and e.ctrlKey will be false, since by the time the keyUp event was fired the user had already released the key.
This was not exactly asked, but for the curious, the following code can handle both single control key presses and double control key presses. It works as follows:
detects a valid single control key event exactly as described in Josiah's answer. If detected, fires ctrlPressed, which:
tracks the two previous timestamps at which a control key was pressed,
initializes a timeout for a particular threshold value (in milliseconds) every time it is fired, and,
if there is a control double click (the timestamp difference is within the threshold), the previous timeout is cancelled, else it isn't.
var lastControlTime = 0,
newControlTime, timeout,
threshold = 200;
function ctrlPressed() {
newControlTime = new Date();
var bool = newControlTime - lastControlTime <= threshold;
if (!bool)
timeout = setTimeout(function() {
if (timeout) {
alert("Single control key pressed");
timeout = undefined;
}
}, threshold);
if (bool) {
alert("Double control key pressed");
clearTimeout(timeout);
timeout = undefined;
}
lastControlTime = newControlTime;
}
function keydownhandler(e) {
var key = e.which || e.keyCode;
controlConsidered = (key == 17);
}
function keyuphandler(e) {
var key = e.which || e.keyCode;
if (key == 17 && controlConsidered) {
ctrlPressed();
}
}
document.addEventListener('keydown', keydownhandler, false);
document.addEventListener('keyup', keyuphandler, false);
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 would like to be able to trap the double key press (for the Char T for example) in order to do some special processing.I would like the key presses to happen fast enough to not be interpreted as two separate presses, just like the double click.
Any ideas how i can achieve this?
When the key(s) are hit, make a note of the time. Then compare it with the time you noted the last time they key(s) were hit.
If the difference is within your threshold, consider it a double. Otherwise, don't. Rough example:
var delta = 500;
var lastKeypressTime = 0;
function KeyHandler(event)
{
if ( String.fromCharCode(event.charCode).toUpperCase()) == 'T' )
{
var thisKeypressTime = new Date();
if ( thisKeypressTime - lastKeypressTime <= delta )
{
doDoubleKeypress();
// optional - if we'd rather not detect a triple-press
// as a second double-press, reset the timestamp
thisKeypressTime = 0;
}
lastKeypressTime = thisKeypressTime;
}
}
Have a variable (perhaps first_press) that you set to true when a keypress event happens, and start a timer that will reset the variable to false after a set amount of time (however fast you want them to press the keys).
In your keypress event, if that variable is true then you have a double press.
Example:
var first_press = false;
function key_press() {
if(first_press) {
// they have already clicked once, we have a double
do_double_press();
first_press = false;
} else {
// this is their first key press
first_press = true;
// if they don't click again in half a second, reset
window.setTimeout(function() { first_press = false; }, 500);
}
}
I know is too late to answer but here goes how I have implemented that:
let pressed;
let lastPressed;
let isDoublePress;
const handleDoublePresss = key => {
console.log(key.key, 'pressed two times');
}
const timeOut = () => setTimeout(() => isDoublePress = false, 500);
const keyPress = key => {
pressed = key.keyCode;
if (isDoublePress && pressed === lastPressed) {
isDoublePress = false;
handleDoublePresss(key);
} else {
isDoublePress = true;
timeOut();
}
lastPressed = pressed;
}
window.onkeyup = key => keyPress(key);