addEventListener('keydown') JavaScript bug? - javascript

I'm trying to create a game in HTML5 and I've come to the point where I need to enable keyboard input.
So Here's my code:
window.addEventListener('keydown', function(e) {
let key = e.keyCode;
console.log(key);
if (key == 37) {
canvas.player.x -= 5;
}
if (key == 38) {
canvas.player.y -= 5;
}
if (key == 39) {
canvas.player.x += 5;
}
if (key == 40) {
canvas.player.y += 5;
}
}, false);
Where canvas is the canvas object and canvas.player the player object. It works, but not very well... Let's say I'm pressing (and holding down) the right arrow key (39) and than press the down arrow key (40) the player is not moving to the right anymore since we last pressed the down arrow key. Works fine. Until I only release the down arrow key while still pressing down the right arrow key. So I never released the right arrow key. Than the player stops and the browser doesn't seem to understand that I'm pressing the right arrow key.
You can easily see this in the console log of this fiddle.
Does anybody has a solution for this problem? A way to detect the keycode anyways?

Keyboard IO.
Generally IO events like the mouse, touch, and keyboards should only be used to get the current state of the devices they are listening to. The job of reacting to input is done in your game.
one way to handle keyboard input is as follows
// this defines what keys you are listening to and
// holds the current state of the key true for down false for up
const keys = {
ArrowUp : false, // list the keyboard keys you want to listen to
ArrowDown : false,
ArrowLeft : false,
ArrowRight : false,
};
// the event listener listens for key events
function keyEvents(e){
if(keys[e.code] !== undefined){ // check if its a key we are listening for
keys[e.code] = event.type === "keydown" ; // set the state up or down
e.preventDefault(); // stop default action
}
}
addEventListener("keyup",keyEvents); // set up the listeners
addEventListener("keydown",keyEvents);
Then in your game's main loop or called from there check the key state and perform the action that the state requires..
if (keys.ArrowDown) { player.y += 5 }
if (keys.ArrowUp) { player.y -= 5 }
if (keys.ArrowLeft) { player.x -= 5 }
if (keys.ArrowRight) { player.x += 5 }

It may be a bug, I thought that it used keyboard events but on mac it seems to repeat the character when held but when typing normally on a mac holding down a key doesn't do that.
I have done a workaround before using a combination of keydown and keyup to set booleans for each of the keys. Then an interval using something like setInterval which checks each of the booleans for each of the keys. If they are true then I carry out the action for that key.

Related

Detect and print key combinations Javascript

I'm currently trying to convert user-provided keyboard combinations into their printable versions (so for example if a user presses shift + a I want to be able to detect it and print the string "shift + a" somewhere so that the user knows which combination he associated with the action.
So far I've been using the provided booleans: ctrlKey, altKey, shiftKey and metaKey, and when they're true I add their printable versions to the final string.
It works... to some extent...
I correctly receive the ctrl flag, but the shift flag is erratic (giving shifts when it should not and not giving shifts when it should, although it works OK for some keys) and the alt flag seems to work only on a few keys and the meta flag does not seem to work at all.
Note that my keyboard works properly, that is, these keys work as intended in normal conditions (the shift key allows me to capitalize, the win key allows me to lock my computer, etc...)
Also note that my keyboard is an azerty.
It behaves the same way on the W3schools example:
https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_event_key_shiftkey
Here is an example:
trying with &, which does not require shift on an azerty
trying with 1, which does require shift on an azerty
So I assume there's something I'm misunderstanding in the way to detect key combinations. Thanks to all those who'll be willing to help.
function logKey(e) {
var modifier1;
var modifier2;
var modifier3;
var modifier4;
var display = "";
if (e.ctrlKey) {
modifier1 = "ctrl + ";
}
else {
modifier1 = "";
}
if (e.altKey) {
modifier2 = "alt + ";
}
else {
modifier2 = "";
}
if (e.shiftKey) {
modifier3 = "shift + ";
}
else {
modifier3 = "";
}
if (e.metaKey) {
modifier4 = "meta + ";
}
else {
modifier4 = "";
}
display = modifier1 + modifier2 + modifier3 + modifier4 + e.key
window.alert(display);
}
window.addEventListener('keydown', logKey);
As skyline3000 hinted in their comment, you are probably better off independently tracking which keys are pressed and then when you need them, simply look at what you've tracked.
Something like this:
const input = document.querySelector('input');
const keysDown = [];
input.addEventListener('keydown', e => {
if (keysDown.indexOf(e.keyCode) === -1) {
keysDown.push(e.keyCode);
}
console.log(keysDown);
});
input.addEventListener('keyup', e => {
const index = keysDown.indexOf(e.keyCode);
if (index !== -1) {
keysDown.splice(index, 1);
}
console.log(keysDown, e);
});
Focus on input to register keys:
<input />
This will let you track them way more precisely and can even track lots of keys down at once.
Then, when you want to show which keys they pressed, you just print out your keysDown list.
So...
I went back to the problem today, and realized that the issue is not even limited to these booleans. Even if I ignore the booleans and try to use keycodes to register modifier keys being pressed, that does not work (using #samanime code), at least not on Firefox, because apparently my firefox does not trigger a keydown/keyup (or keypress) event when I press the modifier keys like ctrl or alt. It does trigger normally on other keys like the character keys for example.
It works on Chrome though.
Is there some compatibility problem that I'm not aware of? Or is it my Firefox that has an issue somehow?
Very simple solution:
let keys = {};
let keysPressed = '';
document.onkeydown = handleKey;
document.onkeyup = handleKey;
function handleKey(e) {
// Indicate key pressed
if (e.type == 'keydown') {
keys[e.key] = true;
}
else {
keys[e.key] = false;
}
// Run on all keys and determine which are pressed
keysPressed = '';
for (const [key, value] of Object.entries(keys)) {
if (value) {
keysPressed += key;
}
}
// Show key presses
document.querySelector('.keys').innerHTML = keysPressed;
}
<div class="keys">Press some keys</div>

Detect alternating key events

I'm writing a script for the following task:
The task is for participants to alternately press the a and b keys on the keyboard as quickly as possible for 10 minutes. Every time a participant successfully press the a key followed by the b key, they should receive a point. Points should only be awarded for alternating key presses, pressing the a key or the b key without alternating between the two should not result in points.
The part of the problem I am asking about is the detection of alternating key events. I attempted this myself and ended up with the code below, but it does not achieve the desired result and I'm getting the following error:
Uncaught ReferenceError: x is not defined
... but I just don't understand what I'm doing wrong.
How can I fix my code and achieve the desired result?
var points = 0;
document.addEventListener('keydown', function(event) {
var x = event.code;
});
document.addEventListener('keydown', function(event) {
if (x == 'KeyA' && event.code == 'KeyB') {
points = points + 1;
document.getElementById("points").innerHTML = points;
}
});
<p>Points: <span id="points">0</span></p>
You only need one event listener, and you need to define the a key press state variable (x) outside of the listener function so that it can be referenced by subsequent executions of the listener function.
You also need to make sure that you reset the a key press variable after the b key press.
it is also generally a good idea to cache your references to elements, rather than selecting the element from the DOM each time your listener function runs, and using textContent instead of innerHTML bypasses the HTML parser.
const target = document.getElementById('points');
var points = 0, x;
document.addEventListener('keydown', function(event) {
if(event.key === 'a') x = true; // If this is an `a` key event, set x to true
if(event.key === 'b') {
// if this is a `b` key event and a was not pressed, return early
if(!x) return;
// otherwise increment the points variable and assign the result to
// the textContent property of the target element
target.textContent = ++points;
// remember to set x to false again
x = false;
}
});
<p>Points: <span id="points">0</span></p>

function on every keypress

I want to run the following block on every keypress of right arrow (39):
$(document).ready(function() {
$(document).keydown(function(e) {
var pos = 10;
if (e.keycode = 39) {
if (pos > 10) {
pos + 10;
}
$(".mario").css('left', pos);
}
});
});
The goal is I am setting a variable, pos, and referencing it in a css() method to move it across the screen in increments of 10, every time the key is pressed the expected behavior is on every right arrow key hit, the object moves across the screen in increments of 10.
It works successfully once, then will not continue to increment. My console is empty/free of errors. I have also tried using keypress:
$(document).ready(function() {
$(document).keypress(function(e) {
var pos = 10;
if (e.keycode = 39) {
if (pos > 10) {
pos + 10;
}
$(".mario").css('left', pos);
}
});
});
To elaborate on this, I also want to add an option to check if the left key is pressed and if so, set it back flush against the screen. I went about this by adding the below block under the closing of the first if statement:
else if (e.keycode = 37) {
$(".mario").css('left', '0');
}
I researched on MDN and it simply states: "The keydown event is fired when a key is pressed down." https://developer.mozilla.org/en-US/docs/Web/Events/keydown
Reading that, what I'm not understanding is why is my event only firing once? How come adding a second condition for the left arrow key doesn't register as an event, if the event is fired every time a key is pressed?
See fiddle here: https://jsfiddle.net/c2fr7rsd/1/
EDIT/UPDATE: As many pointed out, I was/am using an assignment operator = and not a comparison === - I initially tried comparison it doesn't work. Assignment - it works, but now all the keycodes register as 39! Howcome in this instance, the comparison operator doesn't reference the keycode?
UPDATE #2 - using e.which is the correct way to handle keydown events, and using the correct comparison === works
The pos variable should be declared as a global.(outside the function).
Right now, each time that function is called, the variable is set back to 10.
Also logic for checking that variable should be
if(pos >= 10)
And setting the variable should be
pos += 10
Also capitalize C in keyCode and the == will work
https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode
This shows why your logic for if(e.keycode = 39) was returning true
https://www.w3schools.com/js/js_mistakes.asp
Correct code example
$(document).ready(function() {
var pos = 10;
$(document).keypress(function(e) {
if (e.keyCode == 39) {
if (pos >= 10) {
pos += 10;
}
$(".mario").css('left', pos);
}
});
});

Detecting ctrl+z (and other control combos) in paper.js

I'm trying to enable editing commands in my paper.js application (such as CTRL+z for 'undo').
Detecting individual letter keys works great, and I can detect modifier keys that are held during mouse events, but I'm having trouble writing an event handler that detects combinations of CTRL and letter keys.
Based on the examples given by fabric.js, what I would expect is a key handler that looks something like this:
function onKeyDown(event) {
if (event.key == 'z' && event.modifiers.control){
//do a thing!
}
}
However, this doesn't work! Weirdly enough, the conditional block never fires. To investigate this, I wrote the following diagnostic handler...
function onKeyDown(event) {
console.log(event.key);
console.log(event.modifiers.control);
}
... and tried it out with various keyboard inputs with some interesting results:
CTRL key only
Key: control
Control: true
z key only
Key: z
Control: false
z key pressed while holding CTRL
Key:
Control: true
These results suggest that the string returned by event.key is different depending on whether the control modifier is held down when another key is typed. Something weird is happening here!
Based on this, how can I detect both of these keys being pressed at the same time?
Here are a couple of vanilla Javascript solutions that should help you:
Solution 1
Check which keycode was pressed down and if the shiftkey is down using native the event object.
function handleKeyDown(evt) {
if (evt.which === 90 && evt.shiftKey) {
// do a thing!
}
};
Solution 2
Keep a global variable for the detecting if the shift key is down and use that in your keydown handler. You'll also need to reset it with a keyup event handler.
var shiftKeyDown = false;
function handleKeyDown(evt) {
if (evt.which === 17) {
shiftKeyDown = true;
} else if (evt.which === 90 && shiftKeyDown) {
// do a thing!
}
};
function handleKeyUp(evt) {
if (evt.which === 17) {
shiftKeyDown = false;
}
};
TL;DR: You can use event.key.charCodeAt(0) to detect the strange character codes returned by CTRL+Z and other CTRL+key combinations.
As it turns out, the CTRL+z combination is special.
The key being returned in this case...
z key while holding control
Key:
Control: true
... looks like an empty string, because the keycode being passed to the event handler corresponds to the special CTRL+z combination, which results in an unprintable character.
To detect this special character, I modfiied the diagnostic handler...
function onKeyDown(event){
console.log("Key: " + event.key);
console.log("Control: " + event.modifiers.control);
console.log("KeyCode: " + event.key.charCodeAt(0));
}
... and tested the same keyboard combinations as before:
CTRL key only
Key: control
Control: true
KeyCode: 99
z key only
Key: z
Control: false
KeyCode: 122
z key pressed while holding CTRL
Key:
Control: true
KeyCode: 26
This means that the special CTRL key combinations can be detected using an event handler like this:
function onKeyDown(event) {
if (event.key.charCodeAt(0) == 26){ // detect the special CTRL-Z code
// do a thing!
}
}
It should be noted that this approach will not work for detecting the control key on its own, since 99 is NOT the character for CTRL, but rather for "c", the first character in the string "control" returned by event.key. For that, you'll still want to use event.modifiers.control.
function onKeyDown(event) {
if (event.event.ctrlKey && event.key == "z") {
//do something
}
}
This should work.

More efficient to check if a variable is set, then set it, or just constantly set it?

Making a game... More efficient to do this?
if (37 in keysDown) { //left arrow
if (sprite.state != 'left') sprite.state = 'left';
}
or this?
if (37 in keysDown) { //left arrow
sprite.state = 'left';
}
This is being called in my game's update function (constantly, as fast as possible).
Sidenote:
here is my input key checking code.
//input
var keysDown = {};
window.addEventListener('keydown', function(e) {
keysDown[e.keyCode] = true;
});
window.addEventListener('keyup', function(e) {
delete keysDown[e.keyCode];
});
"We should forget about small efficiencies, say about 97% of the time: Premature optimization is the root of all evil."
http://c2.com/cgi/wiki?PrematureOptimization
Those two ways share the same complexity, setting\changing a four chars variable won't be the bottle-neck in your app.
The only things I'm concerned here is the readability of your code, if either way you want sprite.state to have the value left why do you need to check what was the previous value?
(ohh, and it saves like 20 bits of bandwidth which is just like the performance gain here...)
Wouldn't something like this be a lot faster than looping through the keys? You can continuously check a variable very quickly.
var keyleft=false;
window.onkeydown = keydown;
window.onkeyup = keyup;
function keydown(event)
{
var keyCode = ('which' in event) ? event.which : event.keyCode;
if (keyCode==37) keyleft=true;
}
function keyup(event)
{
var keyCode = ('which' in event) ? event.which : event.keyCode;
if (keyCode==37) keyleft=false;
}
No guarantees this code will work, I've forgotten how to do things without jQuery

Categories