Skip to content

fix(core): css-dpr mode should observe content-box, not device-pixel-content-box #2675

Description

@chrisgervang

Summary

When pixelSizeSource: 'css-dpr' is set on CanvasContextProps, the CanvasObserver still observes the canvas with {box: 'device-pixel-content-box'}. This causes major misalignment when used with basemap overlays (Mapbox/MapLibre/Google Maps) during browser zoom changes.

Root Cause

Two issues in the current css-dpr implementation (shipped in 9.3.4 via #2588):

1. Wrong observer box (critical — causes massive shift)

CanvasObserver.start() always uses device-pixel-content-box:

this._resizeObserver.observe(this.props.canvas, {box: 'device-pixel-content-box'});

When the browser zoom changes (Cmd+/-), this observer fires with physical pixel counts that may be stale or racey relative to when Mapbox recalculates its canvas size. Since css-dpr mode ignores the devicePixelContentBoxSize anyway and recalculates from contentBoxSize * dpr, the observer should just watch content-box — which only fires when CSS dimensions actually change, leaving DPR-driven resize to the existing matchMedia + _refreshDevicePixelRatio path.

2. Wrong rounding (minor — causes 1px mismatch)

_getDevicePixelSizeFromResizeEntry uses Math.round:

devicePixelWidth: Math.round(contentBoxSize.inlineSize * devicePixelRatio),

MapLibre uses Math.floor(pixelRatio * width). Mapbox uses pixelRatio * Math.ceil(width) which for integer CSS sizes (the common case) is equivalent to Math.floor after implicit truncation on canvas.width assignment. Using Math.round can produce a value 1px larger than the basemap at certain DPR values.

Proposed Fix

When pixelSizeSource === 'css-dpr':

  1. Pass the mode through to CanvasObserver so it observes with {box: 'content-box'}
  2. Change Math.round to Math.floor in _getDevicePixelSizeFromResizeEntry

Verification

Tested with deck.gl's MapboxOverlay in overlaid mode:

  • Without fix: massive canvas offset at 50%/25% browser zoom, 1px mismatch at 67%/90%
  • With fix: pixel-perfect alignment at all tested zoom levels (25%, 33%, 50%, 67%, 75%, 80%, 90%, 100%, 110%, 125%, 150%, 175%, 200%)
Image 50% Image 25% Image 100% Image 50% Image[ 25% Image

Context

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions