你以为游戏手柄接网页只能靠 getGamepads 每帧轮询?今天 Edge 154 用一个事件把这件事彻底原生化了
做过浏览器游戏的人都踩过这个坑——手柄按下去了,游戏里的人物却慢了半拍才动。不是游戏写得烂,是 navigator.getGamepads() 的原罪:它本质是一个轮询接口,你得在 requestAnimationFrame 里每帧去问一次「现在按键是什么状态」,中间那段延迟是躲不掉的。
这个问题在节奏游戏里最明显——云游戏更严重,每一帧的延迟都要算进用户体验里。Edge 154 今天用 gamepadrawinputchanged 事件把这件事彻底原生化了:手柄一动,事件就飞过来,不用你每帧去问。
旧方案:RAF 轮询的代价
传统写法是这样的:
function readGamepad() {
const gamepads = navigator.getGamepads();
for (const gamepad of gamepads) {
if (!gamepad) continue;
gamepad.buttons.forEach((button, i) => {
if (button.pressed) handleButton(i);
});
gamepad.axes.forEach((axis, i) => {
if (Math.abs(axis) > 0.1) handleAxis(i, axis);
});
}
requestAnimationFrame(readGamepad);
}
你不知道用户什么时候按,只能每帧去查。最理想情况下,你在一帧结束前刚好查到这一次按键——延迟大概 8~16ms(取决于刷新率)。但如果用户恰好在你查完之后才按,那就要等下一帧,最多延迟 16ms。对于 60fps 的游戏,这是可以接受的;但对于云游戏或高精度场景,这 16ms 的不确定性会直接传导到画面上。
新方案:事件驱动,直达硬件
gamepadrawinputchanged 登场后,代码变成了这样:
window.addEventListener("gamepadrawinputchanged", (event) => {
// 哪些轴变化了
for (const axisIndex of event.axesChanged) {
const value = event.gamepad.axes[axisIndex];
handleAxis(axisIndex, value);
}
// 哪些按钮被按下
for (const buttonIndex of event.buttonsPressed) {
handleButton(buttonIndex);
}
// 哪些按钮被松开
for (const buttonIndex of event.buttonsReleased) {
handleButtonRelease(buttonIndex);
}
// 哪些按钮的值变化了(模拟量,如扳机键)
for (const buttonIndex of event.buttonsValueChanged) {
const buttonValue = event.gamepad.buttons[buttonIndex].value;
handleAnalogButton(buttonIndex, buttonValue);
}
});
事件对象直接告诉你「哪些轴动了、哪些键按了/松了」,不用再自己遍历整个 buttons 和 axes 数组做对比。更关键的是:事件在输入状态变化的瞬间就触发,没有 RAF 的帧等待,不会有跨帧延迟。
事件对象的核心字段
axesChanged:本次变化的轴索引
buttonsPressed:本次新按下的按钮索引
buttonsReleased:本次新松开的按钮索引
buttonsValueChanged:值有变化的按钮(含模拟量扳机)
touchesChanged:触摸板/触摸面有变化的触点索引
事件没有合并(no coalescing)——每次输入变化都单独触发,保证你能精确到毫秒级捕捉每一次状态变迁。这对于节奏游戏、光枪游戏、云游戏这类对延迟敏感的应用意义重大。
对谁有用
节奏游戏:节拍误差从一帧(~16ms)压缩到实际输入延迟,几乎等于硬件直达
云游戏:云端每一帧都算钱,减少不确定延迟可以直接降低卡顿感
实时对战:手柄延迟越低,竞技体验越接近原生应用
创意工具:用游戏手柄当 CAD 参数旋钮、音频轨道推子,事件驱动比轮询更省电
兼容性现状
目前 gamepadrawinputchanged 在 Edge 154 稳定版和基于 Chromium 154+ 的浏览器上可用。Chrome 尚未跟进,Firefox/Safari 暂无公开计划。游戏类应用可以把它当作渐进增强:浏览器支持就注册事件,不支持就回退到 RAF 轮询,用户体验自动降级,不会报错。
下一步
如果你正在做游戏类 Web 应用,或者想用手柄做非游戏类交互(控制面板、硬件参数调节),现在可以给现有 RAF 轮询方案加一行 feature detection:
if ("ongamepadrawinputchanged" in window) {
window.addEventListener("gamepadrawinputchanged", onRawInput);
} else {
// 降级到 RAF 轮询
requestAnimationFrame(pollLoop);
}
Edge 154 的 gamepadrawinputchanged 把浏览器游戏的输入延迟从「被动查询」变成了「主动送达」——这件事在游戏手柄这个场景里,等了多少年?大概从 Gamepad API 诞生那天起就有人在等了。
评论区
登录后可评论。