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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DOMElement with a non-zero origin drifts under camera zoom (scene-level only)

1 participant