Version: Phaser 4.2.0 / 4.2.1 (logic also present in 3.x); WebGL & Canvas; any browser.
Description
A DOMElement added directly to the Scene with a non-zero origin (default 0.5, 0.5) drifts from its world position when camera.zoom !== 1, by exactly (originX·width, originY·height)·(1 − camera.zoom). It is sliding bottom-right as you zoom out. The same DOMElement nested in a Container does not drift, so the two render paths are inconsistent.
Minimal example
https://phaser.io/sandbox/f0eb3f05
Root cause (found by Claude Code after some digging around)
In DOMElementCSSRenderer.js, camMatrix.translate(-dx, -dy) bakes the origin offset into the CSS matrix in both branches. The parentMatrix branch then keeps transform-origin: 0% 0% (offset applied once → correct). The else branch also sets transform-origin: (100·origin)% (offset applied twice). The two cancel only when the matrix's linear scale (camera zoom) is 1; under zoom the residual is originX·width·(1 − zoom). The transform-origin percentage lines date to 2019 (v3); the March-2025 v4 refactor preserved the behavior: it typically surfaces once an app drives a real camera zoom on scene-level DOM elements. I tested on v3.80 and the bug was already present.
I'll provide a PR to fix the issue.
Version: Phaser 4.2.0 / 4.2.1 (logic also present in 3.x); WebGL & Canvas; any browser.
Description
A
DOMElementadded directly to the Scene with a non-zero origin (default 0.5, 0.5) drifts from its world position whencamera.zoom !== 1, by exactly(originX·width, originY·height)·(1 − camera.zoom). It is sliding bottom-right as you zoom out. The sameDOMElementnested in aContainerdoes not drift, so the two render paths are inconsistent.Minimal example
https://phaser.io/sandbox/f0eb3f05
Root cause (found by Claude Code after some digging around)
In
DOMElementCSSRenderer.js,camMatrix.translate(-dx, -dy)bakes the origin offset into the CSS matrix in both branches. TheparentMatrixbranch then keepstransform-origin: 0% 0%(offset applied once → correct). The else branch also setstransform-origin: (100·origin)%(offset applied twice). The two cancel only when the matrix's linear scale (camera zoom) is 1; under zoom the residual isoriginX·width·(1 − zoom). The transform-origin percentage lines date to 2019 (v3); the March-2025 v4 refactor preserved the behavior: it typically surfaces once an app drives a real camera zoom on scene-level DOM elements. I tested on v3.80 and the bug was already present.I'll provide a PR to fix the issue.