gamepad api 仅支持现代浏览器(safari 不支持),提供 axes、buttons、id 等基础输入数据,但无振动/陀螺仪/低延迟同步;需用户交互后监听 gamepadconnected 事件并用 requestanimationframe 轮询,且须处理跨设备映射与降级。

HTML 手柄(即通过 Gamepad API 访问的物理游戏手柄)能解决基础游戏控制问题,但仅限于支持该 API 的现代浏览器(Chrome/Firefox/Edge ≥ 91,Safari 不支持),且无法替代原生游戏引擎的输入栈——它不提供振动、陀螺仪、精确电池状态或低延迟帧同步能力。
Gamepad API 能拿到哪些数据?
调用 navigator.getGamepads() 可获取已连接手柄列表,每个 Gamepad 对象包含:
-
id:厂商+型号字符串(如"Xbox Wireless Controller (STANDARD GAMEPAD Vendor: 045e Product: 02ea)"),不可靠,别用于逻辑分支 -
axes:摇杆/扳机轴数组(如左摇杆 X/Y →axes[0],axes[1];右摇杆 →axes[2],axes[3]),范围通常为[-1, 1],需手动做死区过滤 -
buttons:按钮数组(buttons[0]通常是 A/X,但不同手柄映射不一致;推荐用button.mapping判断,但兼容性差) -
connected和timestamp:必须检查,否则可能读到过期或 null 数据
为什么手柄连上了却没反应?
常见原因不是代码写错,而是触发时机和权限问题:
- 页面首次交互(如 click/touch/key)前,
getGamepads()返回空数组 —— 必须等用户主动触发一次事件后再轮询 - Chrome 91+ 强制要求手柄连接需在用户手势后 1 秒内检测,否则忽略 —— 用
gamepadconnected事件监听,而非启动时扫一遍 - Firefox 默认禁用非 HTTPS 页面的 Gamepad API —— 本地开发用
localhost或127.0.0.1可绕过,但file://协议必然失败 - 某些蓝牙手柄(如 Switch Pro)需先配对系统级驱动,网页才能识别 —— 浏览器里显示 “Unknown Gamepad” 很可能只是系统层没通
如何写出稳定的手柄输入循环?
不要用 setInterval 固定频率轮询,而应结合事件 + requestAnimationFrame:
let gamepads = [];
window.addEventListener('gamepadconnected', e => {
gamepads[e.gamepad.index] = e.gamepad;
});
window.addEventListener('gamepaddisconnected', e => {
gamepads[e.gamepad.index] = null;
});
function pollGamepads() {
const pads = navigator.getGamepads();
for (let i = 0; i 0.15
handleInput(pads[i]);
}
}
requestAnimationFrame(pollGamepads);
}
// 等用户点击后才启动
document.body.addEventListener('click', () => {
if (!gamepads.length) pollGamepads();
}, { once: true });
注意:requestAnimationFrame 比 setInterval(16) 更可靠,因它随屏幕刷新率自适应,且页面后台时自动暂停。
真正难的不是读取手柄,而是跨设备统一按钮语义(比如 Xbox 的 A 键 vs PS 的 × 键)、处理摇杆漂移、应对多手柄混用时的 index 错位,以及 Safari 彻底缺席带来的降级方案设计——这些没法靠 API 自动解决,得靠你手动建映射表、存 localStorage 偏好、甚至 fallback 到键盘模拟。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











