We typically use a mouse, a keyboard or touch to navigate the web. What if you could use your gamepad?
You could be using a trackpad because you are sitting on your couch with your laptop or maybe you use a drawing tablet. Maybe you are an early adopter and you are using Leap Motion to control your computer with hand gestures. There are many ways to browse the web and control your computer. Your gamepad may be one of them.
In this article I’ll share how to:
- connect your gamepad in a browser
- listen to gamepad button presses
- control focus on a web page using your gamepad
- add feedback by creating vibrations on a gamepad
So plug in your gamepad (or use Bluetooth) and let’s get started.
The Gamepad API
The Gamepad API is introduced as part of HTML5. The API allows you to use a gamepad inside the browser. In combination with the <canvas>
element, developers can create HTML5 games with gamepad support for better user experiences while playing games in the browser. Creating games is not the only use case for the Gamepad API. A gamepad could also be used as an input device and in this article I’ll explain how.
Side note: the Gamepad API is well supported, but it’s still a working draft, things may change, be removed or be added in the future.
Connect your gamepad
When you connect your gamepad to your computer using USB or Bluetooth, we can start listening to the gamepad API connect and disconnect events:
window.addEventListener('gamepadconnected', function(event) {
// Do something on connect
console.log(event) // see output below
});
window.addEventListener('gamepaddisconnected', function(event) {
// Do something on disconnect
});
The gamepad only can be interacted with after a button is pressed. This is a browser security/privacy feature. Therefore a ‘press a button to start’ feature should be implemented in your project.
The ‘Gamepad Event’ object looks like this when it is logged to the console:
Gamepad layout: axes and buttons
The log shows a whole list of axes (0 - 3) and buttons (0 - 16). How do we know which axis or button belongs to which part of our actual gamepad? Gamepad layouts vary between brands. We’re connecting an Xbox One gamepad which has the following layout:
Listening to button presses
When developing an interface with buttons the .addEventListener()
can be used to listen to the state of buttons and/or links. This isn’t the case when working with a gamepad. The button state has to be checked manually since the gamepad buttons do not emit events. We can do this by checking the pressed
state (Boolean):
const xBoxButtonB = gamepad.buttons[1]
if (xBoxButtonB.pressed) {
doSomethingOnButtonPress();
}
Since we want to do this continuously we need to check this inside a continuous loop, for instance using requestAnimationFrame
:
const rAF = window.mozRequestAnimationFrame || window.requestAnimationFrame;
window.addEventListener('gamepadconnected', function() {
updateLoop();
});
function updateLoop() {
// check button states
rAF(updateLoop);
}
Navigating between focusable elements
In order to use the gamepad as a way to navigate through a web page, the functionality of the keyboard keys need to be simulated. But first all of the focusable items on a page need to be collected.
With the following selector all elements which should be focusable on a web page by default can be selected:
const focusableElements = document.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
There are more elements you can include in the selector, but this is a good baseline to start with.
Moving the focus programmatically
Now in order to place the focus on an element we loop through the NodeList and place focus on the current item by using the element.focus()
method.
Once the focusable elements are stored, they can be looped through and the focus can be placed on the current element.
The following example show how to move the focus from element to element.
let current;
function updateLoop() {
const gamepad = navigator.getGamepads()[0]
const gamepadBumperL = gamepad.buttons[4]
const gamepadBumperR = gamepad.buttons[5]
if (gamepadBumperL.pressed) { prevItem(current) }
if (gamepadBumperR.pressed) { nextItem(current) }
setTimeout(() => rAF(updateLoop), 100)
}
function prevItem(index) {
current = (index - 1) % focusableElements.length
focusableElements[current].focus()
}
function nextItem(index) {
current = (index + 1) % focusableElements.length
focusableElements[current].focus()
}
Note: there’s a setTimeout
function set to throttle the requestAnimationFrame
with 100 milliseconds. Without this, a single button press can be registered multiple times because it is physically impossible to press and release a button within a millisecond.
Clickable elements
If the element is clickable, because it is a link or a button, then a function is needed to do something with those elements. This can be done with the click()
method.
clickItem(index) {
focusableElements[index].click();
}
Keyboard, mouse etc. events have a read only isTrusted
property. This is done so that those events cannot be fired programmatically. Because of this the literal function of a key needs to be translated in Javascript.
Adding feedback with vibrations
Modern gamepads can vibrate. These vibrations can be used to enhance the overall experience of video games. When navigating the web we can also use these vibrations to provide feedback. Modern gamepads are equipped with several Electric Rotation Motors (ERM's) to create vibrations:
We can control the ERMs in the browser using the Gamepad vibrationActuator
API. This contains the type of ‘rumble’ the gamepad has. Modern Xbox and Playstation controllers have a type that is called dual-rumble
. If the browser can't find any motors to work with, then the object will contain null
.
To create a vibration we use the playEffect()
method:
gamepad.vibrationActuator.playEffect('dual-rumble', {
startDelay: 0, // Add a delay in milliseconds
duration: 1000, // Total duration in milliseconds
weakMagnitude: 0.5, // intensity (0-1) of the small ERM
strongMagnitude: 1 // intesity (0-1) of the bigger ERM
});
When using the vibration motors, be aware that the bigger motor takes longer to get up to speed. When you need fast and instant vibrations then you should use the smaller motor instead.
Recap
This example shows how to create basic functions to make a web page navigable with a gamepad. With these steps you’ll achieve this:
- Find focusable elements and store them
- Use the buttons on a gamepad
- Create functions to move the focus around
- Create function to open or activate links and buttons
- Add extra feedback using vibrations
You can find the code on GitHub and view a live demo here. Do you have more ideas for using a gamepad on the web? We’d love to hear about it! You can find us @devoorhoede.
Extra links
- html5gamepad.com: tool to check which buttons and other options your gamepad has.
- Gamepad API on MDN