mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 09:30:45 +00:00
fix: prevent game zoom runaway after browser zoom shortcut (#3532)
## Description: When the user changes browser zoom using keyboard shortcuts (cmd+Plus / cmd+Minus on Mac, ctrl+Plus / ctrl+Minus on Windows), the game would start zooming in uncontrollably afterwards. The zoom could not be stopped — only temporarily countered by zooming out, but the game would continue zooming in on its own. **Root cause:** Two issues combined: 1. When cmd+Plus/Minus is pressed, the browser intercepts the event and handles its own zoom. The `keydown` fires and adds `Equal`/`Minus` to `activeKeys`, but the `keyup` is swallowed by the browser and never fires — leaving the key stuck. The 1ms `moveInterval` then continuously emits `ZoomEvent` forever. 2. The `onScroll` handler was passing browser-generated synthetic wheel events (fired with `ctrlKey: true` and large `deltaY`) directly to the game zoom logic, amplified by 10x. **Fix:** - Skip adding `Minus`/`Equal` to `activeKeys` when a meta/ctrl modifier is held (browser zoom combo) - On `MetaLeft`/`MetaRight` keyup, explicitly clear any stuck zoom keys - Clear all `activeKeys` on `window blur` as a general safety net - In `onScroll`, ignore synthetic wheel events with `ctrlKey: true` and `|deltaY| > 10` (browser zoom events vs real pinch-to-zoom which has small deltas) - Add a minimum delta threshold of 2 for regular scroll to cut off macOS momentum tail events ## Please complete the following: - [x] I have added screenshots for all UI updates - [x] I process any text displayed to the user through translateText() and I've added it to the en.json file - [x] I have added relevant tests to the test directory - [x] I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced ## Please put your Discord username so you can be contacted if a bug or regression is found: fghjk_60845 Co-authored-by: Ivan <batsulin.i@mail.ru>
This commit is contained in:
@@ -266,6 +266,19 @@ export class InputHandler {
|
||||
this.eventBus.emit(new MouseMoveEvent(e.clientX, e.clientY));
|
||||
}
|
||||
});
|
||||
// Clear all tracked keys when the window loses focus so keys that had
|
||||
// their keyup swallowed by the browser (e.g. cmd+zoom) don't stay stuck.
|
||||
// Also release the hold-to-view state and any active pointer/drag state
|
||||
// so the alternate view and drags aren't left latched when focus returns.
|
||||
window.addEventListener("blur", () => {
|
||||
this.activeKeys.clear();
|
||||
if (this.alternateView) {
|
||||
this.alternateView = false;
|
||||
this.eventBus.emit(new AlternateViewEvent(false));
|
||||
}
|
||||
this.pointerDown = false;
|
||||
this.pointers.clear();
|
||||
});
|
||||
this.pointers.clear();
|
||||
|
||||
this.moveInterval = setInterval(() => {
|
||||
@@ -310,13 +323,15 @@ export class InputHandler {
|
||||
|
||||
if (
|
||||
this.activeKeys.has(this.keybinds.zoomOut) ||
|
||||
this.activeKeys.has("Minus")
|
||||
this.activeKeys.has("Minus") ||
|
||||
this.activeKeys.has("NumpadSubtract")
|
||||
) {
|
||||
this.eventBus.emit(new ZoomEvent(cx, cy, this.ZOOM_SPEED));
|
||||
}
|
||||
if (
|
||||
this.activeKeys.has(this.keybinds.zoomIn) ||
|
||||
this.activeKeys.has("Equal")
|
||||
this.activeKeys.has("Equal") ||
|
||||
this.activeKeys.has("NumpadAdd")
|
||||
) {
|
||||
this.eventBus.emit(new ZoomEvent(cx, cy, -this.ZOOM_SPEED));
|
||||
}
|
||||
@@ -358,7 +373,19 @@ export class InputHandler {
|
||||
this.eventBus.emit(new ConfirmGhostStructureEvent());
|
||||
}
|
||||
|
||||
// Don't track zoom keys when a meta/ctrl modifier is held — that means
|
||||
// the browser is handling its own zoom (cmd+/cmd-) and the keyup will
|
||||
// never fire, which would leave the key stuck in activeKeys forever.
|
||||
// Also covers numpad zoom shortcuts (Ctrl+NumpadAdd/NumpadSubtract).
|
||||
const isBrowserZoomCombo =
|
||||
(e.metaKey || e.ctrlKey) &&
|
||||
(e.code === "Minus" ||
|
||||
e.code === "Equal" ||
|
||||
e.code === "NumpadAdd" ||
|
||||
e.code === "NumpadSubtract");
|
||||
|
||||
if (
|
||||
!isBrowserZoomCombo &&
|
||||
[
|
||||
this.keybinds.moveUp,
|
||||
this.keybinds.moveDown,
|
||||
@@ -372,6 +399,8 @@ export class InputHandler {
|
||||
"ArrowRight",
|
||||
"Minus",
|
||||
"Equal",
|
||||
"NumpadAdd",
|
||||
"NumpadSubtract",
|
||||
this.keybinds.attackRatioDown,
|
||||
this.keybinds.attackRatioUp,
|
||||
this.keybinds.centerCamera,
|
||||
@@ -390,6 +419,24 @@ export class InputHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// When the meta (cmd) or ctrl key is released, any keys that were held
|
||||
// simultaneously will have had their keyup swallowed by the browser
|
||||
// (e.g. cmd+Plus for browser zoom). Clear zoom-related keys to
|
||||
// prevent them staying stuck in activeKeys.
|
||||
if (
|
||||
e.code === "MetaLeft" ||
|
||||
e.code === "MetaRight" ||
|
||||
e.code === "ControlLeft" ||
|
||||
e.code === "ControlRight"
|
||||
) {
|
||||
this.activeKeys.delete("Minus");
|
||||
this.activeKeys.delete("Equal");
|
||||
this.activeKeys.delete("NumpadAdd");
|
||||
this.activeKeys.delete("NumpadSubtract");
|
||||
this.activeKeys.delete(this.keybinds.zoomIn);
|
||||
this.activeKeys.delete(this.keybinds.zoomOut);
|
||||
}
|
||||
|
||||
if (e.code === this.keybinds.toggleView) {
|
||||
e.preventDefault();
|
||||
this.alternateView = false;
|
||||
@@ -537,8 +584,27 @@ export class InputHandler {
|
||||
const realCtrl =
|
||||
this.activeKeys.has("ControlLeft") ||
|
||||
this.activeKeys.has("ControlRight");
|
||||
const ratio = event.ctrlKey && !realCtrl ? 10 : 1; // Compensate pinch-zoom low sensitivity
|
||||
this.eventBus.emit(new ZoomEvent(event.x, event.y, event.deltaY * ratio));
|
||||
if (event.ctrlKey) {
|
||||
if (!realCtrl) {
|
||||
// Pinch-to-zoom gesture (trackpad): small deltas, amplify.
|
||||
// Ignore large deltas — those are browser zoom shortcuts (cmd+/cmd-)
|
||||
// which fire synthetic wheel events we don't want to handle.
|
||||
if (Math.abs(event.deltaY) <= 10) {
|
||||
this.eventBus.emit(
|
||||
new ZoomEvent(event.x, event.y, event.deltaY * 10),
|
||||
);
|
||||
}
|
||||
}
|
||||
// Always return when ctrlKey is set — whether it's a real ctrl scroll,
|
||||
// a pinch gesture, or a browser zoom event, none should reach the
|
||||
// regular scroll path below.
|
||||
return;
|
||||
}
|
||||
// Regular scroll wheel: ignore tiny residual momentum events that macOS
|
||||
// keeps sending after a gesture ends (especially after browser zoom changes
|
||||
// devicePixelRatio, which can cause these to accumulate into runaway zoom).
|
||||
if (Math.abs(event.deltaY) < 2) return;
|
||||
this.eventBus.emit(new ZoomEvent(event.x, event.y, event.deltaY));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user