fix: take all measurements inside animation callback for label sync

Move containerRect and svgRect lookups inside zoomSpring.to() callbacks
so all measurements are taken at the same moment as the magnifier viewBox
calculation. This ensures perfect synchronization between the magnifier
content and the overlay labels.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Thomas Hallock 2025-11-25 16:57:10 -06:00
parent 94d1cdfcb5
commit 2191e0732b
2 changed files with 21 additions and 12 deletions

View File

@ -2436,8 +2436,6 @@ export function MapRenderer({
{/* Debug: Bounding box labels as HTML overlays - positioned using animated values */} {/* Debug: Bounding box labels as HTML overlays - positioned using animated values */}
{SHOW_DEBUG_BOUNDING_BOXES && {SHOW_DEBUG_BOUNDING_BOXES &&
containerRef.current &&
svgRef.current &&
debugBoundingBoxes.map((bbox) => { debugBoundingBoxes.map((bbox) => {
const importance = bbox.importance ?? 0 const importance = bbox.importance ?? 0
let strokeColor = '#888888' let strokeColor = '#888888'
@ -2450,23 +2448,19 @@ export function MapRenderer({
strokeColor = '#ffcc00' strokeColor = '#ffcc00'
} }
// Get magnifier dimensions // Parse viewBox - these are stable values from mapData
const containerRect = containerRef.current!.getBoundingClientRect()
const magnifierWidth = containerRect.width * 0.5
const magnifierHeight = magnifierWidth / 2
// Parse viewBox
const viewBoxParts = mapData.viewBox.split(' ').map(Number) const viewBoxParts = mapData.viewBox.split(' ').map(Number)
const viewBoxX = viewBoxParts[0] || 0 const viewBoxX = viewBoxParts[0] || 0
const viewBoxY = viewBoxParts[1] || 0 const viewBoxY = viewBoxParts[1] || 0
const viewBoxWidth = viewBoxParts[2] || 1000 const viewBoxWidth = viewBoxParts[2] || 1000
const viewBoxHeight = viewBoxParts[3] || 1000 const viewBoxHeight = viewBoxParts[3] || 1000
// Calculate bbox center // Calculate bbox center in SVG coordinates
const bboxCenterSvgX = bbox.x + bbox.width / 2 const bboxCenterSvgX = bbox.x + bbox.width / 2
const bboxCenterSvgY = bbox.y + bbox.height / 2 const bboxCenterSvgY = bbox.y + bbox.height / 2
// Use animated interpolation to sync with magnifier viewBox // Use animated interpolation to sync with magnifier viewBox
// ALL measurements must be taken inside the callback to stay in sync
return ( return (
<animated.div <animated.div
key={`mag-bbox-label-${bbox.regionId}`} key={`mag-bbox-label-${bbox.regionId}`}
@ -2474,32 +2468,47 @@ export function MapRenderer({
position: 'absolute', position: 'absolute',
// Calculate position using the same spring that controls the magnifier viewBox // Calculate position using the same spring that controls the magnifier viewBox
left: zoomSpring.to((zoom: number) => { left: zoomSpring.to((zoom: number) => {
const containerRect = containerRef.current?.getBoundingClientRect()
const svgRect = svgRef.current?.getBoundingClientRect() const svgRect = svgRef.current?.getBoundingClientRect()
if (!svgRect || !cursorPosition) return '-9999px' if (!containerRect || !svgRect || !cursorPosition) return '-9999px'
// Magnifier dimensions
const magnifierWidth = containerRect.width * 0.5
// Convert cursor to SVG coordinates (same as magnifier viewBox calc)
const scaleX = viewBoxWidth / svgRect.width const scaleX = viewBoxWidth / svgRect.width
const svgOffsetX = svgRect.left - containerRect.left const svgOffsetX = svgRect.left - containerRect.left
const cursorSvgX = (cursorPosition.x - svgOffsetX) * scaleX + viewBoxX const cursorSvgX = (cursorPosition.x - svgOffsetX) * scaleX + viewBoxX
// Magnified viewport in SVG coordinates
const magnifiedWidth = viewBoxWidth / zoom const magnifiedWidth = viewBoxWidth / zoom
const magnifiedViewBoxX = cursorSvgX - magnifiedWidth / 2 const magnifiedViewBoxX = cursorSvgX - magnifiedWidth / 2
// Position of bbox center relative to magnified viewport (0-1)
const relativeX = (bboxCenterSvgX - magnifiedViewBoxX) / magnifiedWidth const relativeX = (bboxCenterSvgX - magnifiedViewBoxX) / magnifiedWidth
if (relativeX < 0 || relativeX > 1) return '-9999px' if (relativeX < 0 || relativeX > 1) return '-9999px'
return `${relativeX * magnifierWidth}px` return `${relativeX * magnifierWidth}px`
}), }),
top: zoomSpring.to((zoom: number) => { top: zoomSpring.to((zoom: number) => {
const containerRect = containerRef.current?.getBoundingClientRect()
const svgRect = svgRef.current?.getBoundingClientRect() const svgRect = svgRef.current?.getBoundingClientRect()
if (!svgRect || !cursorPosition) return '-9999px' if (!containerRect || !svgRect || !cursorPosition) return '-9999px'
// Magnifier dimensions (2:1 aspect ratio)
const magnifierWidth = containerRect.width * 0.5
const magnifierHeight = magnifierWidth / 2
// Convert cursor to SVG coordinates (same as magnifier viewBox calc)
const scaleY = viewBoxHeight / svgRect.height const scaleY = viewBoxHeight / svgRect.height
const svgOffsetY = svgRect.top - containerRect.top const svgOffsetY = svgRect.top - containerRect.top
const cursorSvgY = (cursorPosition.y - svgOffsetY) * scaleY + viewBoxY const cursorSvgY = (cursorPosition.y - svgOffsetY) * scaleY + viewBoxY
// Magnified viewport in SVG coordinates
const magnifiedHeight = viewBoxHeight / zoom const magnifiedHeight = viewBoxHeight / zoom
const magnifiedViewBoxY = cursorSvgY - magnifiedHeight / 2 const magnifiedViewBoxY = cursorSvgY - magnifiedHeight / 2
// Position of bbox center relative to magnified viewport (0-1)
const relativeY = (bboxCenterSvgY - magnifiedViewBoxY) / magnifiedHeight const relativeY = (bboxCenterSvgY - magnifiedViewBoxY) / magnifiedHeight
if (relativeY < 0 || relativeY > 1) return '-9999px' if (relativeY < 0 || relativeY > 1) return '-9999px'

View File

@ -14,7 +14,7 @@ export interface CropOverrides {
export const customCrops: CropOverrides = { export const customCrops: CropOverrides = {
world: { world: {
europe: '401.07 94.52 225.17 278.11', 'north-america': '-14.59 139.66 378.17 314.90',
} }
} }