Skip to content

Fix DOMElement origin drift under camera zoom#7330

Open
moufmouf wants to merge 1 commit into
phaserjs:masterfrom
moufmouf:fix/domelement-origin-zoom-drift
Open

Fix DOMElement origin drift under camera zoom#7330
moufmouf wants to merge 1 commit into
phaserjs:masterfrom
moufmouf:fix/domelement-origin-zoom-drift

Conversation

@moufmouf

@moufmouf moufmouf commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

This PR:

  • Fixes a bug

Fixes #7329.

Describe the changes

A scene-level DOMElement with a non-zero origin (the default is 0.5, 0.5) drifted
away from its world position when camera.zoom !== 1, by originX * width * (1 - zoom)
horizontally and the equivalent vertically. The same DOMElement nested in a Container
did not drift — the two render paths were inconsistent.

Cause: in DOMElementCSSRenderer, camMatrix.translate(-dx, -dy) bakes the origin
offset into the CSS matrix in both branches. The parentMatrix branch correctly leaves
transform-origin at 0% 0% (offset applied once). The scene-level else branch also
set transform-origin: (100 * origin)%, applying the offset a second time. The two only
cancel when the camera-matrix scale is 1, so under camera zoom the residual
originX * width * (1 - zoom) shows up as drift.

Fix: make the scene-level path match the (correct) container path — keep
transform-origin at 0% 0% and bake the scale-aware offset into the matrix. The
scaleX / scaleY factor the parentMatrix branch already applied is correct for the
scene-level branch too, so it's hoisted up rather than duplicated:

var dx = src.width * src.originX * src.scaleX;
var dy = src.height * src.originY * src.scaleY;
// ...
if (parentMatrix)
{
    camMatrix.multiply(parentMatrix);
}

camMatrix.translate(-dx, -dy);

(tx / ty now stay '0%', so transform-origin is 0% 0% on both paths.)

The container-nested element is unchanged (no drift before or after), and scaled / rotated
scene-level elements now also stay correctly anchored at any zoom because the baked offset
is scale-aware.

A minimal repro is available here and can be copy/pasted:

https://phaser.io/sandbox/f0eb3f05

Disclaimer: this PR was mostly written by Claude Code but manually checked to be working (so AI assisted but hopefully not AI slop :) )

A scene-level DOMElement with a non-zero origin (the default is 0.5, 0.5)
drifted away from its world position when the camera zoom was not 1, by
`originX * width * (1 - zoom)` horizontally and the equivalent vertically.
The same DOMElement nested in a Container did not drift.

DOMElementCSSRenderer always bakes the origin offset into the CSS matrix via
`camMatrix.translate(-dx, -dy)`. The parentMatrix branch correctly leaves
`transform-origin` at 0% 0%, so the offset is applied once. The scene-level
(else) branch additionally set `transform-origin: (100 * origin)%`, applying
the offset a second time; the two only cancel when the camera zoom is 1.

Make the scene-level path match the container path: bake the scale-aware
offset into the matrix and keep `transform-origin` at 0% 0%. Scaled and
rotated scene-level elements now stay correctly anchored at any zoom too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant