-
Notifications
You must be signed in to change notification settings - Fork 7.1k
Description
Hello, I think I might have found a bug, though I’m not entirely sure.
I’m using Phaser 3 inside a React app. The game runs smoothly at first, but as soon as I scroll the page (even by just 1px), it starts stuttering every half second.
After a lot of debugging, GPT-5-CODEX suggested a fix that solved the problem. The game now runs perfectly smoothly even after scrolling.
What’s interesting is that it claimed this was a Phaser issue. That’s where my own knowledge ends, so either it’s completely wrong and just AI nonsense, or it’s an actual bug. If it’s not a real bug, I apologize for the noise, but if it is, I figured it was worth sharing here.
I also asked it to generate a GitHub issue, which I’ve included below.
Title
ScaleManager getParentBounds triggers perpetual refresh() once page is scrolled
Body
-
Description: When running Phaser 3.90.0 with scale.mode = Phaser.Scale.FIT inside a normal scrolling page, the game begins hitching every ~500 ms after the user scrolls vertically. The
scale manager keeps calling refresh() even though layout and canvas size are unchanged, causing repeated style recalculations and visible stutter. -
Reproduction:
- Create a vanilla Phaser project (no React) with config { type: Phaser.AUTO, scale: { mode: Phaser.Scale.FIT, autoCenter: Phaser.Scale.CENTER_BOTH, width: 1280, height: 720 } }.
- Mount the canvas in a tall HTML page so the document can scroll (e.g., wrap in a div with standard margins).
- Load the page in Chrome, scroll down even a few pixels, stop scrolling, and leave the game running.
- Observe frame hitches every half-second; ScaleManager.refresh() fires on each poll.
-
Expected: After scrolling stops, the scale manager should consider the layout stable and avoid invoking refresh() unless the parent element actually resizes.
-
Actual: ScaleManager.step() calls refresh() every resizeInterval (default 500 ms) indefinitely, even with no further scroll or resize events.
-
Analysis: In ScaleManager.getParentBounds() the cached canvasBounds are stored in page coordinates (rect.left + window.pageXOffset), but the subsequent comparison uses canvasRect.x / y
from getBoundingClientRect(), which are viewport-relative. Once window.pageYOffset is non-zero, canvasRect.y !== canvasBounds.y remains true forever, so refresh() runs on every poll. -
Workaround: Override getParentBounds() to normalize the coordinates or ignore pure scroll-offset changes. For example:
const original = scale.getParentBounds.bind(scale);
scale.getParentBounds = function patched() {
const changed = original();
const rect = this.canvas.getBoundingClientRect();
const scrollX = window.pageXOffset || 0;
const scrollY = window.pageYOffset || 0;
this.canvasBounds.setTo(
rect.left + scrollX - (document.documentElement.clientLeft || 0),
rect.top + scrollY - (document.documentElement.clientTop || 0),
rect.width,
rect.height
);
return changed;
};
After applying this, refresh() stops firing unless the actual size changes. -
Environment: Phaser 3.90.0, Chrome 124 / Windows 11