Gamepad

The Gamepad API, Widely available since 2019, reads USB and Bluetooth controllers. For privacy, a pad appears only after the user presses a button, which fires gamepadconnected. There are no input events: poll navigator.getGamepads() every frame. With mapping === 'standard', buttons[0] is the bottom face button and axes[0] the left stick's horizontal position (-1 to 1).

Polling a controller in a game loopJavaScript
(function loop() {
  const pad = navigator.getGamepads()[0];              // [null, null, null, null] until a press
  if (pad && Math.abs(pad.axes[0]) > 0.1) hero.x += pad.axes[0] * 5;   // 0.1 dead zone
  if (pad?.buttons[0].pressed) pad.vibrationActuator?.playEffect('dual-rumble', { duration: 150 });
  requestAnimationFrame(loop);
})();

Rumble works in Chrome 1 and Safari 16.4 10 +.