Handling Multiple Key Presses at Once in Vanilla JavaScript (for Game Controllers)

Nicky Dover
6 min readJan 16, 2020

So, you’re building a JavaScript game.

Maybe it’s for a 48 hour game jam.

Maybe you’re a new coder who’s been told that building a game will teach you about the importance of code organization and JavaScript classes.

Maybe you’re nostalgic for the Golden Age of Flash games and want to give HTML5 Canvas a try.

On second thought, maybe Flash should stay dead.

But at some point, you spun up a new index.html, wrote some code, requested some animation frames, and eventually, even got something to move along a screen.

You’ve got this far, congratulations!

But then you ended up at my blog, because suddenly you realized something like:

  • My sprite freezes every time it has to shoot a laser.
  • My pong paddle keeps stopping once my friend’s pong paddle starts moving.
  • My character refuses to move diagonally, like a castle in chess.

You, my friend, have become a victim of a weird habit of JavaScript’s keydown handler: When two keys are being pressed at the same time, it only acknowledges key repeat (ie. continues to fire while the key’s being held down) on the last one to be pressed. It forgets about the other finger until you lift its counterpart back up.

(Note: Hitting two keys exactly simultaneously isn’t a thing, and is definitely not the solution to this problem.)

Let’s demonstrate the problem. I’m going to add this quick bit of code to the Chrome Dev Tools:

document.addEventListener(“keydown”, (e) => console.log(e.keyCode))

This will simply print out whatever’s being pressed to the console. Now when I then press the up key on my keyboard (keyCode 38):

We get 38 returned with an increasing counter next to it as it keeps firing really quickly.

Now, if I were to continue to press up but also hold down right(keyCode 39), you’d expect to see the console alternate back and forth with 38 then 39 then 38 then 38 then 39 then 38 then 39 then 38, at an incredibly fast rate to show off the fact that it’s a computer and it can say numbers real fast.

However, what actually happens is this:

It just starts ignoring whatever finger you started pressing down slightly earlier entirely, and this right here is why your sprite isn’t going diagonally.

Fortunately, I’ve made a lot of games that have had to solve this problem…

Pong Clones
Divekick Clones
Bullet Hell Games that Pull From Website Articles

…And I have a pattern that has served me well in solving this pattern for all these games.

The solution involves tracking keyup events as well as keydown events! Because while we can’t rely on keydown events to keep firing on key repeat when users are pressing multiple keys at once, we can comfortably know that a key is pressed until a keyup event is fired on that key (when a user lifts their finger up [or fist, or face, or whatever]).

So this pattern works in three steps:

  • Create a controller object to track each key that’s pressed down, as well as the function to be executed for each key while that key is pressed down.
  • Build two event listeners, one for keydown and one for keyup, that updates the controller object whenever a key is initially pressed down, and when that key becomes unpressed.
  • Define a function that calls every function whose key is currently tracked as pressed in that controller, and call that function in your animation function.

We’ll start by building the controller, a Plain Old JavaScript Object. Each key-value pair will represent one key that we want the users to be able to press down to be able to accomplish something.

So we’ll define each (keyboard) key by their keyCode and use that keyCode as a (JavaScript) key in the controller object.

Each key needs to be able to do two things: Tell us if they’re pressed, and tell us which function to execute if they are pressed. So we’ll set each key’s associated value to another Plain Old JavaScript Object with two keys, pressed and func.

This is what a controller looks like for a two-player game of Pong, where we want player 1 to be able to move their paddle up and down with W and S, and player 2 to be able to move their paddle up and down with UP and DOWN.

const controller = {  87: {pressed: false, func: player1.movePaddleUp},  83: {pressed: false, func: player1.movePaddleDown},  38: {pressed: false, func: player2.movePaddleUp},  40: {pressed: false, func: player2.movePaddleDown},}

In this case, player1 and player2 are two instances of a paddle class that I’ve defined. You’ll see that all keys’ pressed value starts at false.

The next step is to write two event listeners that change the key’s pressed value to true when it’s true and false when it’s false.

The following two event listeners for keydown and keyup check to see if a pressed key is one that exists in our controller, and then set that key’s pressed value to true if the event is a keydown event and false if the event is a keyup event.

document.addEventListener("keydown", (e) => {  if(controller[e.keyCode]){    controller[e.keyCode].pressed = true  }})document.addEventListener("keyup", (e) => {  if(controller[e.keyCode]){    controller[e.keyCode].pressed = false  }})

With these event listeners in place, any button that’s being pressed down will be marked as pressed in our controller, even if more than one button is being pressed at once! They’ll simply be marked as not pressed the next time a key up is registered on their respective keys. We’ve solved the problem!

The final step is simply to write a function that makes each key do something.

The following function accomplishes exactly what we need. It says that for every key in the controller, check to see if it’s pressed, and then if so, run the function we associated with that key.

const executeMoves = () => {  Object.keys(controller).forEach(key=> {    controller[key].pressed && controller[key].func()  })}

Once this code is done, you can simply drop this function at the top of your animation callback function (the one that is usually called in window.requestAnimationFrame or, if you’re a monster, in setInterval.)

Here’s what it looks like in my animation function for Pong.

const animate = () => {  ctx.clearRect(0, 0, gameInfo.canvasWidth, gameInfo.canvasHeight);  executeMoves()  checkPaddleCollisions()  checkWallCollisions()  checkWin()  moveBall()  paintBall()  renderPaddles()  window.requestAnimationFrame(animate)}window.requestAnimationFrame(animate)

And that’s it! All our keys are now firing their events even if you’re pressing more than one at once!

The rest of the code for this particular animation function relates to Pong code that won’t be discussed in this blog (I do give a lecture on Building Pong in JavaScript fairly regularly at the Flatiron School), but the GitHub repo for my last completed Pong game from lecture is available here: https://github.com/NickyEXE/pong-100919

--

--