Blog

Navigating the web with a gamepad

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:

Code showing Gamepad object
Gamepad object

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:

Xbox One gamepad button layout
Xbox One gamepad button 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:

Xbox One gamepad electric rotation motor layout
Xbox One gamepad electric rotation motor layout

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.

Vibration pattern graph
Vibration pattern graph

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:

  1. Find focusable elements and store them
  2. Use the buttons on a gamepad
  3. Create functions to move the focus around
  4. Create function to open or activate links and buttons
  5. 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

← All blog posts

Also in love with the web?

For us, that’s about technology and user experience. Fast, available for all, enjoyable to use. And fun to build. This is how our team bands together, adhering to the same values, to make sure we achieve a solid result for clients both large and small. Does that fit you?

Join our team