HTML5 introduced many of the necessary components for rich, interactive game development. Technologies like <canvas> , WebGL, <audio> ,和 <video> , along with JavaScript implementations, have matured to the point where they can now support many tasks previously requiring native code. The Gamepad API is a way for developers and designers to access and use gamepads and other game controllers.

手柄 API introduces new events on the Window object for reading gamepad and controller (hereby referred to as gamepad ) state. In addition to these events, the API also adds a Gamepad object, which you can use to query the state of a connected gamepad, and a navigator.getGamepads() method which you can use to get a list of gamepads known to the page.

Connecting to a gamepad

When a new gamepad is connected to the computer, the focused page first receives a gamepadconnected event. If a gamepad is already connected when the page loaded, the gamepadconnected event is dispatched to the focused page when the user presses a button or moves an axis.

In Firefox, gamepads are only exposed to a page when the user interacts with one with the page visible. This helps prevent gamepads being used for fingerprinting the user. Once one gamepad has been interacted with, other gamepads that are connected will automatically be visible.

可以使用 gamepadconnected 像这样:

window.addEventListener("gamepadconnected", function(e) {
  console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.",
    e.gamepad.index, e.gamepad.id,
    e.gamepad.buttons.length, e.gamepad.axes.length);
});
					

Each gamepad has a unique ID associated with it, which is available on the event's gamepad 特性。

Disconnecting a gamepad

When a gamepad is disconnected, and if a page has previously received data for that gamepad (e.g. gamepadconnected ), a second event is dispatched to the focused window, gamepaddisconnected :

window.addEventListener("gamepaddisconnected", function(e) {
  console.log("Gamepad disconnected from index %d: %s",
    e.gamepad.index, e.gamepad.id);
});
					

The gamepad's index property will be unique per-device connected to the system, even if multiple controllers of the same type are used. The index property also functions as the index into the 数组 返回通过 Navigator.getGamepads() .

var gamepads = {};
function gamepadHandler(event, connecting) {
  var gamepad = event.gamepad;
  // Note:
  // gamepad === navigator.getGamepads()[gamepad.index]
  if (connecting) {
    gamepads[gamepad.index] = gamepad;
  } else {
    delete gamepads[gamepad.index];
  }
}
window.addEventListener("gamepadconnected", function(e) { gamepadHandler(e, true); }, false);
window.addEventListener("gamepaddisconnected", function(e) { gamepadHandler(e, false); }, false);
					

This previous example also demonstrates how the gamepad property can be held after the event has completed — a technique we will use for device state querying later.

Querying the Gamepad object

As you can see, the gamepad events discussed above include a gamepad property on the event object, which returns a Gamepad object. We can use this in order to determine which gamepad (i.e., its ID) had caused the event, since multiple gamepads might be connected at once. We can do much more with the Gamepad object, including holding a reference to it and querying it to find out which buttons and axes are being pressed at any one time. Doing so is often desirable for games or other interactive web pages that need to know the state of a gamepad now vs. the next time an event fires.

Performing such checks tends to involve using the Gamepad object in conjunction with an animation loop (e.g., requestAnimationFrame ), where developers want to make decisions for the current frame based on the state of the gamepad or gamepads.

Navigator.getGamepads() method returns an array of all devices currently visible to the webpage, as Gamepad objects (the first value is always null ,所以 null will be returned if there are no gamepads connected.) This can then be used to get the same information. For example, the first code example above you be rewritten as shown below:

window.addEventListener("gamepadconnected", function(e) {
  var gp = navigator.getGamepads()[e.gamepad.index];
  console.log("Gamepad connected at index %d: %s. %d buttons, %d axes.",
    gp.index, gp.id,
    gp.buttons.length, gp.axes.length);
});
					

Gamepad object's properties are as follows:

  • id : A string containing some information about the controller. This is not strictly specified, but in Firefox it will contain three pieces of information separated by dashes ( - ): two 4-digit hexadecimal strings containing the USB vendor and product id of the controller, and the name of the controller as provided by the driver. This information is intended to allow you to find a mapping for the controls on the device as well as display useful feedback to the user.
  • index : An integer that is unique for each gamepad currently connected to the system. This can be used to distinguish multiple controllers. Note that disconnecting a device and then connecting a new device may reuse the previous index.
  • 映射 : A string indicating whether the browser has remapped the controls on the device to a known layout. Currently there is only one supported known layout — the standard gamepad . If the browser is able to map controls on the device to that layout the 映射 property will be set to the string standard .
  • connected : A boolean indicating whether the gamepad is still connected to the system. If this is so the value is True ; if not, it is False .
  • buttons : An array of GamepadButton objects representing the buttons present on the device. Each GamepadButton 拥有 pressed value 特性:
    • pressed property is a boolean indicating whether the button is currently pressed ( true ) or unpressed ( false ).
    • value property is a floating point value used to enable representing analog buttons, such as the triggers on many modern gamepads. The values are normalized to the range 0.0..1.0, with 0.0 representing a button that is not pressed, and 1.0 representing a button that is fully pressed.
  • axes : An array representing the controls with axes present on the device (e.g. analog thumb sticks). Each entry in the array is a floating point value in the range -1.0 - 1.0, representing the axis position from the lowest value (-1.0) to the highest value (1.0).
  • timestamp : This returns a DOMHighResTimeStamp representing the last time the data for this gamepad was updated, allowing developers to determine if the axes and button data have been updated from the hardware. The value must be relative to the navigationStart 属性在 PerformanceTiming interface. Values are monotonically increasing, meaning that they can be compared to determine the ordering of updates, as newer values will always be greater than or equal to older values. Note that this property is not currently supported in Firefox.

注意 : The Gamepad object is available on the gamepadconnected event rather than the Window object itself, for security reasons. Once we have a reference to it, we can query its properties for information about the current state of the gamepad. Behind the scenes, this object will be updated every time the gamepad's state changes.

Using button information

Let's look at a simple example that displays connection information for one gamepad (it ignores subsequent gamepad connections) and allows you to move a ball around the screen using the four gamepad buttons on the right hand side of the gamepad. You can view the demo live ,和 find the source code on Github.

To start with, we declare some variables: The gamepadInfo paragraph that the connection info is written into, the ball that we want to move, the start variable that acts as the ID for requestAnimation Frame a and b variables that act as position modifiers for moving the ball, and the shorthand variables that will be used for the requestAnimationFrame() and cancelAnimationFrame() cross browser forks.

var gamepadInfo = document.getElementById("gamepad-info");
var ball = document.getElementById("ball");
var start;
var a = 0;
var b = 0;
					

Next we use the gamepadconnected event to check for a gamepad being connected. When one is connected, we grab the gamepad using Navigator.getGamepads() [0] , print information about the gamepad into our gamepad info div , and fire the gameLoop() function that starts the whole ball movement process up.

window.addEventListener("gamepadconnected", function(e) {
  var gp = navigator.getGamepads()[e.gamepad.index];
  gamepadInfo.innerHTML = "Gamepad connected at index " + gp.index + ": " + gp.id + ". It has " + gp.buttons.length + " buttons and " + gp.axes.length + " axes.";
  gameLoop();
});
					

Now we use the gamepaddisconnected event to check if the gamepad is disconnected again. If so, we stop the requestAnimationFrame() loop (see below) and revert the gamepad information back to what it was originally.

window.addEventListener("gamepaddisconnected", function(e) {
  gamepadInfo.innerHTML = "Waiting for gamepad.";
  cancelRequestAnimationFrame(start);
});
					

Chrome does things differently here. Instead of constantly storing the gamepad's latest state in a variable it only stores a snapshot, so to do the same thing in Chrome you have to keep polling it and then only use the Gamepad object in code when it is available. We have done this below using Window.setInterval() ; once the object is available the gamepad info is outputted, the game loop is started, and the interval is cleared using Window.clearInterval() . Note that in older versions of Chrome Navigator.getGamepads() is implemented with a webkit prefix. We attempt to detect and handle both the prefixed version and the standard version of the function for backwards compatibility.

var interval;
if (!('ongamepadconnected' in window)) {
  // No gamepad events available, poll instead.
  interval = setInterval(pollGamepads, 500);
}
function pollGamepads() {
  var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []);
  for (var i = 0; i < gamepads.length; i++) {
    var gp = gamepads[i];
    if (gp) {
      gamepadInfo.innerHTML = "Gamepad connected at index " + gp.index + ": " + gp.id +
        ". It has " + gp.buttons.length + " buttons and " + gp.axes.length + " axes.";
      gameLoop();
      clearInterval(interval);
    }
  }
}
					

Now on to the main game loop. In each execution of the loop we check if one of four buttons is being pressed; if so, we update the values of the a and b movement variables appropriately, then update the left and top properties, changing their values to the current values of a and b respectively. This has the effect of moving the ball around the screen.  In current versions of Chrome (version 34 as of this writing) the button values are stored as an array of double values, instead of GamepadButton objects. This is fixed in development versions.

After all this is done, we use our requestAnimationFrame() to request the next animation frame, running gameLoop() 再次。

function buttonPressed(b) {
  if (typeof(b) == "object") {
    return b.pressed;
  }
  return b == 1.0;
}
function gameLoop() {
  var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads : []);
  if (!gamepads) {
    return;
  }
  var gp = gamepads[0];
  if (buttonPressed(gp.buttons[0])) {
    b--;
  } else if (buttonPressed(gp.buttons[2])) {
    b++;
  }
  if (buttonPressed(gp.buttons[1])) {
    a++;
  } else if (buttonPressed(gp.buttons[3])) {
    a--;
  }
  ball.style.left = a * 2 + "px";
  ball.style.top = b * 2 + "px";
  start = requestAnimationFrame(gameLoop);
}
					

Complete example: Displaying gamepad state

此范例展示如何使用 Gamepad object, as well as the gamepadconnected and gamepaddisconnected events in order to display the state of all gamepads connected to the system. You can find a working demo and look at the full source code on Github.

var haveEvents = 'ongamepadconnected' in window;
var controllers = {};
function connecthandler(e) {
  addgamepad(e.gamepad);
}
function addgamepad(gamepad) {
  controllers[gamepad.index] = gamepad;
  var d = document.createElement("div");
  d.setAttribute("id", "controller" + gamepad.index);
  var t = document.createElement("h1");
  t.appendChild(document.createTextNode("gamepad: " + gamepad.id));
  d.appendChild(t);
  var b = document.createElement("div");
  b.className = "buttons";
  for (var i = 0; i < gamepad.buttons.length; i++) {
    var e = document.createElement("span");
    e.className = "button";
    //e.id = "b" + i;
    e.innerHTML = i;
    b.appendChild(e);
  }
  d.appendChild(b);
  var a = document.createElement("div");
  a.className = "axes";
  for (var i = 0; i < gamepad.axes.length; i++) {
    var p = document.createElement("progress");
    p.className = "axis";
    //p.id = "a" + i;
    p.setAttribute("max", "2");
    p.setAttribute("value", "1");
    p.innerHTML = i;
    a.appendChild(p);
  }
  d.appendChild(a);
  // See https://github.com/luser/gamepadtest/blob/master/index.html
  var start = document.getElementById("start");
  if (start) {
    start.style.display = "none";
  }
  document.body.appendChild(d);
  requestAnimationFrame(updateStatus);
}
function disconnecthandler(e) {
  removegamepad(e.gamepad);
}
function removegamepad(gamepad) {
  var d = document.getElementById("controller" + gamepad.index);
  document.body.removeChild(d);
  delete controllers[gamepad.index];
}
function updateStatus() {
  if (!haveEvents) {
    scangamepads();
  }
  var i = 0;
  var j;
  for (j in controllers) {
    var controller = controllers[j];
    var d = document.getElementById("controller" + j);
    var buttons = d.getElementsByClassName("button");
    for (i = 0; i < controller.buttons.length; i++) {
      var b = buttons[i];
      var val = controller.buttons[i];
      var pressed = val == 1.0;
      if (typeof(val) == "object") {
        pressed = val.pressed;
        val = val.value;
      }
      var pct = Math.round(val * 100) + "%";
      b.style.backgroundSize = pct + " " + pct;
      if (pressed) {
        b.className = "button pressed";
      } else {
        b.className = "button";
      }
    }
    var axes = d.getElementsByClassName("axis");
    for (i = 0; i < controller.axes.length; i++) {
      var a = axes[i];
      a.innerHTML = i + ": " + controller.axes[i].toFixed(4);
      a.setAttribute("value", controller.axes[i] + 1);
    }
  }
  requestAnimationFrame(updateStatus);
}
function scangamepads() {
  var gamepads = navigator.getGamepads ? navigator.getGamepads() : (navigator.webkitGetGamepads ? navigator.webkitGetGamepads() : []);
  for (var i = 0; i < gamepads.length; i++) {
    if (gamepads[i]) {
      if (gamepads[i].index in controllers) {
        controllers[gamepads[i].index] = gamepads[i];
      } else {
        addgamepad(gamepads[i]);
      }
    }
  }
}
window.addEventListener("gamepadconnected", connecthandler);
window.addEventListener("gamepaddisconnected", disconnecthandler);
if (!haveEvents) {
 setInterval(scangamepads, 500);
}
					

规范

规范 状态 注释
Gamepad
The definition of 'Gamepad' in that specification.
工作草案 Initial defintion

浏览器兼容性

The compatibility table on this page is generated from structured data. If you'd like to contribute to the data, please check out https://github.com/mdn/browser-compat-data and send us a pull request.

更新 GitHub 上的兼容性数据
桌面 移动
Chrome Edge Firefox Internet Explorer Opera Safari Android webview Chrome for Android Firefox for Android Opera for Android Safari on iOS Samsung Internet
Gamepad Chrome 35
35
不支持 21 — 34 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Edge ≤18 Firefox 29
29
不支持 24 — 28 Disabled
Disabled ). To change preferences in Firefox, visit
IE No Opera 22
22
不支持 15 — 21 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Safari 10.1 WebView Android No Chrome Android 35
35
不支持 25 — 34 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Firefox Android 32 Opera Android 22
22
不支持 14 — 21 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Safari iOS 10.3 Samsung Internet Android 4.0
4.0
不支持 2.0 — 3.0 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Gamepad() 构造函数 Chrome 35 Edge 12 Firefox 29
29
不支持 24 — 28 Disabled
Disabled ). To change preferences in Firefox, visit
IE No Opera 22 Safari 10.1 WebView Android No Chrome Android 35 Firefox Android 32 Opera Android 22 Safari iOS 10.3 Samsung Internet Android 4.0
axes Chrome 35
35
不支持 21 — 34 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Edge 12 Firefox 29
29
不支持 24 — 28 Disabled
Disabled ). To change preferences in Firefox, visit
IE No Opera 22
22
不支持 15 — 21 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Safari 10.1 WebView Android No Chrome Android 35
35
不支持 25 — 34 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Firefox Android 32 Opera Android 22
22
不支持 14 — 21 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Safari iOS 10.3 Samsung Internet Android 4.0
4.0
不支持 2.0 — 3.0 Prefixed
Prefixed Implemented with the vendor prefix: webkit
buttons Chrome 35
35
不支持 21 — 34 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Edge 12 Firefox 29
29
不支持 24 — 28 Disabled
Disabled ). To change preferences in Firefox, visit
IE No Opera 22
22
不支持 15 — 21 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Safari 10.1 WebView Android No Chrome Android 35
35
不支持 25 — 34 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Firefox Android 32 Opera Android 22
22
不支持 14 — 21 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Safari iOS 10.3 Samsung Internet Android 4.0
4.0
不支持 2.0 — 3.0 Prefixed
Prefixed Implemented with the vendor prefix: webkit
connected Chrome 35
35
不支持 25 — 34 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Edge 12 Firefox 29
29
不支持 24 — 28 Disabled
Disabled ). To change preferences in Firefox, visit
IE No Opera 22
22
不支持 15 — 21 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Safari 10.1 WebView Android No Chrome Android 35
35
不支持 25 — 34 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Firefox Android 32 Opera Android 22
22
不支持 14 — 21 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Safari iOS 10.3 Samsung Internet Android 4.0
4.0
不支持 2.0 — 3.0 Prefixed
Prefixed Implemented with the vendor prefix: webkit
displayId 弃用 Chrome ? — 80 Disabled
不支持 ? — 80 Disabled
Only works on desktop in an experimental version of Chrome (other builds won't return any devices when Navigator.getVRDisplays() is invoked).
Disabled Until version 80 (exclusive): this feature is behind the WebVR preference. To change preferences in Chrome, visit chrome://flags.
Edge 15 Firefox 55
55
Windows support was enabled in Firefox 55.
64
macOS support was enabled in Firefox 64.
IE No Opera ? Safari No WebView Android No Chrome Android ? — 80
不支持 ? — 80
Currently supported only by Google Daydream.
Firefox Android 55 Opera Android ? Safari iOS No Samsung Internet Android Yes
Yes
Currently supported only by Google Daydream.
hand Chrome No Edge 15 — 79 Firefox Yes Disabled
Yes Disabled
The flag is enabled by default in Firefox Nightly and Beta, versions 55 and above.
Disabled ). To change preferences in Firefox, visit about:config.
IE No Opera No Safari No WebView Android No Chrome Android No Firefox Android No Opera Android No Safari iOS No Samsung Internet Android No
hapticActuators Chrome No Edge 15 — 79 Firefox Yes Disabled
Yes Disabled
The flag is enabled by default in Firefox Nightly and Beta, versions 55 and above.
Disabled ). To change preferences in Firefox, visit about:config.
IE No Opera No Safari No WebView Android No Chrome Android No Firefox Android No Opera Android No Safari iOS No Samsung Internet Android No
id Chrome 35
35
不支持 21 — 34 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Edge 12 Firefox 29
29
不支持 24 — 28 Disabled
Disabled ). To change preferences in Firefox, visit
IE No Opera 22
22
不支持 15 — 21 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Safari No WebView Android No Chrome Android 35
35
不支持 25 — 34 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Firefox Android 32 Opera Android 22
22
不支持 14 — 21 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Safari iOS No Samsung Internet Android 4.0
4.0
不支持 2.0 — 3.0 Prefixed
Prefixed Implemented with the vendor prefix: webkit
index Chrome 35
35
不支持 21 — 34 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Edge 12 Firefox 29
29
不支持 24 — 28 Disabled
Disabled ). To change preferences in Firefox, visit
IE No Opera 22
22
不支持 15 — 21 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Safari 10.1 WebView Android No Chrome Android 35
35
不支持 25 — 34 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Firefox Android 32 Opera Android 22
22
不支持 14 — 21 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Safari iOS 10.3 Samsung Internet Android 4.0
4.0
不支持 2.0 — 3.0 Prefixed
Prefixed Implemented with the vendor prefix: webkit
映射 Chrome 35
35
不支持 21 — 34 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Edge 12 Firefox 29
29
不支持 24 — 28 Disabled
Disabled ). To change preferences in Firefox, visit
IE No Opera 22
22
不支持 15 — 21 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Safari 10.1 WebView Android No Chrome Android 35
35
不支持 25 — 34 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Firefox Android 32 Opera Android 22
22
不支持 14 — 21 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Safari iOS 10.3 Samsung Internet Android 4.0
4.0
不支持 2.0 — 3.0 Prefixed
Prefixed Implemented with the vendor prefix: webkit
pose Chrome No Edge 15 — 79 Firefox Yes Disabled
Yes Disabled
The flag is enabled by default in Firefox Nightly and Beta, versions 55 and above.
Disabled ). To change preferences in Firefox, visit about:config.
IE No Opera No Safari No WebView Android No Chrome Android No Firefox Android No Opera Android No Safari iOS No Samsung Internet Android No
timestamp Chrome 35
35
不支持 21 — 34 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Edge 12 Firefox 29
29
不支持 24 — 28 Disabled
Disabled ). To change preferences in Firefox, visit
IE No Opera 22
22
不支持 15 — 21 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Safari 10.1 WebView Android No Chrome Android 35
35
不支持 25 — 34 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Firefox Android 32 Opera Android 22
22
不支持 14 — 21 Prefixed
Prefixed Implemented with the vendor prefix: webkit
Safari iOS 10.3 Samsung Internet Android 4.0
4.0
不支持 2.0 — 3.0 Prefixed
Prefixed Implemented with the vendor prefix: webkit
vibrationActuator 非标 Chrome 68 Edge 79 Firefox No IE No Opera 55 Safari No WebView Android No Chrome Android 68 Firefox Android No Opera Android 48 Safari iOS No Samsung Internet Android 10.0

图例

完整支持

完整支持

不支持

不支持

兼容性未知 ?

兼容性未知

实验。期望将来行为有所改变。

实验。期望将来行为有所改变。

非标。预期跨浏览器支持较差。

非标。预期跨浏览器支持较差。

弃用。不要用于新网站。

弃用。不要用于新网站。

见实现注意事项。

用户必须明确启用此特征。

用户必须明确启用此特征。

要求使用供应商前缀或不同名称。

要求使用供应商前缀或不同名称。

元数据

  • 最后修改:
  1. 手柄 API
  2. 指南
    1. Using the Gamepad API
    2. Implementing controls using the Gamepad API
  3. 接口
    1. Gamepad
    2. GamepadButton
    3. GamepadEvent
  4. 特性
    1. Window.ongamepadconnected
    2. Window.ongamepaddisconnected
  5. 方法
    1. Navigator.getGamepads()
  6. 事件
    1. Window : gamepadconnected
    2. Window : gamepaddisconnected

版权所有  © 2014-2026 乐数软件    

工业和信息化部: 粤ICP备14079481号-1