https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API
Here's some sample code, which you can put in an index.html and run with bun index.html:
<!doctype html>
<title>Game controller</title>
<h1>Game controller</h1>
<p>Minimal code to connect controller and use d-pad to move, 'x' to 'fire'. See <a href="https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API">MDN for more</a> or <a href="/example.html">see all the input sources</a> your gamepad has.</p>
<canvas id="canvas" style="border: 1px black solid;"></canvas>
<script>
let ctx = canvas.getContext('2d')
let hero = {
x: canvas.width / 2,
y: canvas.height / 2,
speed: 3,
active: false
}
gameLoop()
function gameLoop () {
ctx.clearRect(0, 0, canvas.width, canvas.height)
let gamepads = navigator.getGamepads()
for (let gamepad of gamepads) {
if (gamepad) {
console.log(gamepad.buttons)
if (gamepad.buttons[12].pressed) hero.y-= hero.speed
if (gamepad.buttons[13].pressed) hero.y+= hero.speed
if (gamepad.buttons[14].pressed) hero.x-= hero.speed
if (gamepad.buttons[15].pressed) hero.x+= hero.speed
hero.active = gamepad.buttons[0].pressed
}
}
ctx.fillStyle = hero.active ? 'red' : 'black'
ctx.fillRect(hero.x, hero.y, 10, 10)
requestAnimationFrame(gameLoop)
}
</script>
... this code is also running online at https://gamepad.deno.dev/.
And some more sample code:
<!doctype html>
<h1>Connect gamepad to see buttons and other inputs</h1>
<p>Code straight from <a href="https://developer.mozilla.org/en-US/docs/Web/API/Gamepad_API/Using_the_Gamepad_API#complete_example_displaying_gamepad_state">MDN for more</a>.</p>
<script>
let loopStarted = false;
window.addEventListener("gamepadconnected", (evt) => {
addGamepad(evt.gamepad);
});
window.addEventListener("gamepaddisconnected", (evt) => {
removeGamepad(evt.gamepad);
});
function addGamepad(gamepad) {
const d = document.createElement("div");
d.setAttribute("id", `controller${gamepad.index}`);
const t = document.createElement("h1");
t.textContent = `gamepad: ${gamepad.id}`;
d.append(t);
const b = document.createElement("ul");
b.className = "buttons";
gamepad.buttons.forEach((button, i) => {
const e = document.createElement("li");
e.className = "button";
e.textContent = `Button ${i}`;
b.append(e);
});
d.append(b);
const a = document.createElement("div");
a.className = "axes";
gamepad.axes.forEach((axis, i) => {
const p = document.createElement("progress");
p.className = "axis";
p.setAttribute("max", "2");
p.setAttribute("value", "1");
p.textContent = i;
a.append(p);
});
d.appendChild(a);
// See https://github.com/luser/gamepadtest/blob/master/index.html
const start = document.querySelector("#start");
if (start) {
start.style.display = "none";
}
document.body.append(d);
if (!loopStarted) {
requestAnimationFrame(updateStatus);
loopStarted = true;
}
}
function removeGamepad(gamepad) {
document.querySelector(`#controller${gamepad.index}`).remove();
}
function updateStatus() {
for (const gamepad of navigator.getGamepads()) {
if (!gamepad) continue;
const d = document.getElementById(`controller${gamepad.index}`);
const buttonElements = d.getElementsByClassName("button");
for (const [i, button] of gamepad.buttons.entries()) {
const el = buttonElements[i];
const pct = `${Math.round(button.value * 100)}%`;
el.style.backgroundSize = `${pct} ${pct}`;
if (button.pressed) {
el.textContent = `Button ${i} [PRESSED]`;
el.style.color = "#42f593";
el.className = "button pressed";
} else {
el.textContent = `Button ${i}`;
el.style.color = "#2e2d33";
el.className = "button";
}
}
const axisElements = d.getElementsByClassName("axis");
for (const [i, axis] of gamepad.axes.entries()) {
const el = axisElements[i];
el.textContent = `${i}: ${axis.toFixed(4)}`;
el.setAttribute("value", axis + 1);
}
}
requestAnimationFrame(updateStatus);
}
</script>