A while ago I was talking to the author of the aforementioned Jamir that the WASD
controls they implemented weren’t that practical for me due to the AZERTY
keyboard layout which I use.
Thankfully, the fix is pretty small and easy to roll out …
~
WASD
vs. AZERTY
To control the position of characters in games, the gaming industry has settled on using the WASD
keys to do so. These keys are laid out similar fashion to the arrow keys, can be controlled with the left hand, and keep your right hand free to control the mouse to turn and look around.
On QWERTY
, using WASD
works fine, as these keys are placed in line with a rather natural position of your left hand’s fingers:
But on AZERTY
— the default keyboard layout in Belgium — it would look like this:
To not have to flex our fingers in some very weird ways, us Belgians have adopted the use of ZQSD
, which maps to the same physical keys as WASD
on QWERTY
.
In most games this difference in keyboard layout is automatically handled, and pressing ZQSD
works fine for me and my fellow AZERTY
users. In Jamir, however, that’s not the case, and we’re required to press the key that has the label W
in order to move forward.
~
The problem with KeyboardEvent.key
The culprit is that Jamir’s code is basing itself on KeyboardEvent.key
to determine which key was pressed. Since KeyboardEvent.key
represents the label that appears on the key, that will map to different physical positions per keyboard layout. You can see this in the images above: the W
key has moved between QWERTY
and AZERTY
Pressing the key with the label w
on your keyboard — or any keyboard — will always show a value of w
for KeyboardEvent.key
, independent that key’s physical location on the keyboard.
To play nice with AZERTY
, Jamir would have to maintain separate list of KeyboardEvent.key
values to respond to ZQSD
. Same for DVORAK
, Maltron, QWERTZ
, Colemak, JCUKEN, or any other keyboard layout …
- User has
QWERTY
?
→ Listen forKeyboardEvent.key
W
to move forward. - User has
AZERTY
?
→ Listen forKeyboardEvent.key
Z
to move forward. - User has …?
→ Listen forKeyboardEvent.key
???
to move forward.
Seems like a tedious job to do and maintain 🫤
~
Keyboard Layout-Independent WASD
Thankfully there’s an easier solution than keeping track of multiple keyboard mappings: instead of evaluating KeyboardEvent.key
, evaluate KeyboardEvent.code
, as that represents the physical key that’s being pressed.
See the Pen
Keyboard Event Debugger by Bramus (@bramus)
on CodePen.
Testing with the CodePen above, using several keyboard layouts, you can see that the value for KeyboardEvent.code
remains the same when pressing the same physical key:
-
QWERTY
keyboard, pressingW
:{"keyCode":87, "key":"w", "which":87, "code":"KeyW"}
-
AZERTY
keyboard, pressingZ
:{"keyCode":90, "key":"z", "which":90, "code":"KeyW"}
If you have your code check the value of KeyboardEvent.code
, the WASD
controls will automatically adapt themselves to become ZQSD
on AZERTY
, ,AOE
on DVORAK
, etc. — yay! 🎉
- User has
QWERTY
?
→ Listen forKeyboardEvent.code
KeyW
to move forward. - User has
AZERTY
?
→ Listen forKeyboardEvent.code
KeyW
to move forward. - User has any keyboard layout?
→ Listen forKeyboardEvent.code
KeyW
to move forward.
☝️ The thing that has to click here is that there’s a difference between the physical position of a key (exposed through KeyboardEvent.code
) and the label on the key (exposed through KeyboardEvent.key
). E.g. Z
on AZERTY
and W
on QWERTY
are the same physical key ("KeyW"
), but they have a different label (z
or w
, depending on the layout).
~
Press W
/Z
/,
to move forward
With KeyboardEvent.code
in place, our controls will now play nice with all keyboard layouts, but it can now become a bit difficult to tell the user which key they need to press:
- Using
QWERTY
?
→ PressW
to move forward. - Using
AZERTY
?
→ PressZ
to move forward. - Using
DVORAK
?
→ Press,
to move forward.
👨🏫 Again, note that these all map to the same physical key ("KeyW"
) being pressed.
Thankfully there’s the experimental Keyboard.getLayoutMap()
that can help us out here. Using it, we can translate a KeyboardEvent.code
to it’s accompanying label:
const keyboardLayoutMap = await navigator.keyboard.getLayoutMap();
const forwardKey = keyboardLayoutMap.get('KeyW');
console.log(forwardKey);
The code above will return w
on QWERTY
, z
on AZERTY
.
👨🔬 Keyboard.getLayoutMap()
is only supported in Chromium based browsers at the time of writing.
~
When not to use KeyboardEvent.code
Note that it’s not required everywhere to use KeyboardEvent.code
. If you have an undo feature in your app that’s triggered by pressing the z
key, then you do need to listen to KeyboardEvent.key
.
It’s only in situations where the physical keys matter — such as WASD
controls in games, a piano, etc. — that you need to resort to KeyboardEvent.code
.
~
Want more?
If you want more: in the latest episode of HTTP 203, Jake and Ada tap into keyboard events and this use-case. Besides covering non-QWERTY
keyboard layouts, they also cover virtual keyboards, compositions, swipey keyboards, etc.
~
🔥 Like what you see? Want to stay in the loop? Here's how:
Leave a comment