JavaScript Input Buffering: a simple project to show delayed keyboard events
Talking about Javascript.
When we talk about game development or interactive web applications, keyboard input is one of the first mechanics we need to master, and also one of the most underestimated.
Most developers know how to check if a key is pressed, but very few truly understand how to track input, how to record events with precise timing, or how to replay actions with controlled delays.
So I built a small experiment to visualize all of this in the simplest way possible.
It’s not the first time I am playing with input delay, as you can see in the post Simulate a keyboard input delay with Phaser, but this time I am going to use plain JavaScript.
In this tutorial, we’ll create a clean WASD input visualizer that shows your keyboard actions twice: once in real time, and once with a one second delay.
The goal is not to make a game (at the moment), but to understand some of the core concepts every game developer should know: how keydown and keyup listeners work, how to avoid repeated events, how to measure time accurately with performance.now(), and how to build an update loop using requestAnimationFrame.
Look at the result:
Focus the frame, press W, A, S, D keys and watch the real time output in the upper keyboard, while the lower keyboard will show keys pressed with a two seconds delay.
This is the completely commented source code, and there’s also a video explaining the making of this script.
Please subscribe to my newborn YouTube channel to help this format to grow.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript Delayed Input</title>
<style>
body {
display: flex;
flex-direction: column;
justify-content: center;
height: 100vh;
margin: 0;
align-items: center;
background-color: #1f1f1f;
color: white;
}
.label {
font-size: 24px;
font-family: sans-serif;
margin: 60px 0 20px 0;
}
.key {
font-size:36px;
font-family: sans-serif;
font-weight: bold;
border: 2px solid white;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
}
.keys {
display: grid;
gap: 10px;
grid-template-columns: repeat(3, 80px);
grid-template-rows: repeat(2, 80px);
}
.pressed {
background-color: #4caf50;
}
</style>
</head>
<body>
<div class ="label">LIVE INPUT</div>
<div class="keys">
<div></div>
<div class="key" id="live-w">W</div>
<div></div>
<div class="key" id="live-a">A</div>
<div class="key" id="live-s">S</div>
<div class="key" id="live-d">D</div>
</div>
<div class ="label">BUFFERED INPUT</div>
<div class="keys">
<div></div>
<div class="key" id="buffered-w">W</div>
<div></div>
<div class="key" id="buffered-a">A</div>
<div class="key" id="buffered-s">S</div>
<div class="key" id="buffered-d">D</div>
</div>
<script>
// allowed keys to track
const allowedKeys = ['w', 'a', 's', 'd'];
// array to store all key events
const keyEvents = [];
// delay, in milliseconds
const delay = 2000;
// last buffer index processed
let lastIndex = 0;
// function to be called when a key is down
document.addEventListener('keydown', event => {
// get the down key and turn it to lowercase
const key = event.key.toLowerCase();
// if the key is not an allowed key, or if the even is repeating (key continuously pressed)
// do nothing
if (!allowedKeys.includes(key) || event.repeat) {
return;
}
// record event with timestamp
keyEvents.push({
key,
type: 'down',
time: performance.now()
})
// get the proper id according to key pressed
const liveDiv = document.getElementById('live-' + key);
// add 'pressed' class to id
liveDiv.classList.add('pressed');
});
// function to be called when a key is up
document.addEventListener('keyup', event => {
// get the down key and turn it to lowercase
const key = event.key.toLowerCase();
// if the key is not an allowed key, or if the even is repeating (key continuously pressed)
// do nothing
if (!allowedKeys.includes(key) || event.repeat) {
return;
}
// record event with timestamp
keyEvents.push({
key,
type: 'up',
time: performance.now()
})
// get the proper id according to key pressed
const liveDiv = document.getElementById('live-' + key);
// remove 'pressed' class to id
liveDiv.classList.remove('pressed');
});
function processBuffer() {
// get current timestamp
const now = performance.now();
// while there are unprocessed key events...
while (lastIndex < keyEvents.length) {
// get next event to be processed
const event = keyEvents[lastIndex];
// if the event should be shown now...
if (now - event.time >= delay) {
// get the element according to key pressed or released
const bufferDiv = document.getElementById('buffered-' + event.key);
// add or remove 'pressed' class according to event type
if (event.type == 'down') {
bufferDiv.classList.add('pressed');
}
else {
bufferDiv.classList.remove('pressed');
}
// increment lastIndex
lastIndex ++;
}
else {
break;
}
}
// request next animation frame, then call processBuffer
requestAnimationFrame(processBuffer);
}
// call processBuffer function
processBuffer();
</script>
</body>
</html>Now you learned how to manage delayed input. How would you turn it into something playable?
Never miss an update! Subscribe, and I will bother you by email only when a new game or full source code comes out.