fix(editor): anchor walkthrough crosshair to the canvas, not the viewport#243
Open
b9llach wants to merge 1 commit intopascalorg:mainfrom
Open
fix(editor): anchor walkthrough crosshair to the canvas, not the viewport#243b9llach wants to merge 1 commit intopascalorg:mainfrom
b9llach wants to merge 1 commit intopascalorg:mainfrom
Conversation
…port
`FirstPersonOverlay`'s crosshair was wrapped in a `fixed inset-0` div
that covers the whole browser viewport. The inner `flex items-center
justify-center` then centered the crosshair on the viewport midpoint
— but the actual R3F Canvas occupies only the right-hand region of
the page, because the editor's v2 layout puts the sidebar on the
left. Result: the crosshair shows up noticeably off-centre in the
user's actual first-person view, sitting over the sidebar instead of
the scene they're aiming at.
This switches the crosshair wrapper from `fixed inset-0` to a fixed
container whose `left` / `top` / `width` / `height` match the
canvas's `getBoundingClientRect()`. A `ResizeObserver` on the
canvas plus `resize` and `scroll` listeners keep the rect synced
so the crosshair tracks sidebar collapses, panel toggles, window
drags, and zoom. If the canvas hasn't been measured yet (first
render before the effect runs) the wrapper falls back to `inset: 0`
so the crosshair still appears, just full-screen-centred until the
next frame.
Selector is `document.querySelector('canvas')` — the editor has one
live canvas in the DOM at a time; the thumbnail generator's canvas
is offscreen-only and doesn't appear in the DOM, so the query
reliably resolves to the main viewer canvas. If a future layout
grows a second DOM canvas this will need to become more specific.
The exit button (top-right) and controls hint (bottom-centre) still
use their original `fixed`-edge positioning on purpose — they're
edge-anchored chrome that should stay put regardless of sidebar
state.
hellozzm
pushed a commit
to hellozzm/editor
that referenced
this pull request
Apr 16, 2026
…alorg#220) Sync monorepo PRs pascalorg#243 and pascalorg#248 to the open-source editor. Move/Rotate Building: - New move-building-tool with grid snapping, R/T rotation, and Esc cancel - Floating building action menu with move button - Building helper with keyboard shortcut hints - BuildingRenderer now applies position and rotation from node data Building-Relative Coordinate System: - GridEvent gains localPosition (building-local coords) - use-grid-events computes building-local intersection from building mesh - ToolManager wraps building-relative tools in a building-local <group> - All tools (wall, slab, ceiling, stair, roof, zone, polygon-editor, placement coordinator) updated to use event.localPosition - Placement coordinator adds worldToBuildingLocal helper for cursor position conversion 2D Floorplan Fix: - SVG layers wrapped in <g transform=rotate(...)> for building rotation - Pointer-to-plan conversion un-rotates when building is rotated - Grid events from 2D include localPosition Store Changes: - useEditor movingNode union includes BuildingNode - NodeActionMenu onDelete is now optional (buildings can move but not delete)
afurm
reviewed
Apr 16, 2026
| import { useCallback, useEffect, useRef } from 'react' | ||
| import { useCallback, useEffect, useRef, useState } from 'react' | ||
| import { Euler, Vector3 } from 'three' | ||
| import useEditor from '../../store/use-editor' |
There was a problem hiding this comment.
Race condition: update() is called synchronously before observer.observe(canvas) is set up. If ResizeObserver fires its initial callback immediately on observe() (permitted by the spec), update runs before observer is assigned — so observer.disconnect() in cleanup silently no-ops. Move observer.observe(canvas) to the line before update().
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
`FirstPersonOverlay`'s crosshair is wrapped in a `fixed inset-0` div that covers the whole browser viewport. The inner `flex items-center justify-center` then centres the crosshair on the viewport midpoint — but the actual R3F Canvas occupies only the right-hand region of the page, because the editor's v2 layout puts the sidebar on the left. Result: when you enter first-person mode the crosshair shows up noticeably off-centre in your actual view, sitting over the sidebar instead of the scene you're aiming at.
Reproduces on any editor layout where the Canvas is not flush against the left edge of the window (v2 layout, any split view, any scene with a sidebar visible).
Fix
Switch the crosshair wrapper from `fixed inset-0` to a fixed container whose `left` / `top` / `width` / `height` match the canvas's `getBoundingClientRect()`. A `ResizeObserver` on the canvas plus `resize` and `scroll` listeners keep the rect synced so the crosshair follows:
If the canvas hasn't been measured yet (first render before the effect runs), the wrapper falls back to `inset: 0` so the crosshair still appears — just full-screen-centred until the next frame. This avoids a visible flash of "no crosshair" at the moment first-person mode is entered.
The exit button (top-right) and controls hint (bottom-centre) are unchanged on purpose — they're edge-anchored chrome that should stay put regardless of sidebar state.
Scope
How to test
Checklist