Skip to main content
Version: 0.x.x

Camera & Viewport

The engine uses a virtual camera system to navigate the infinite grid. Control the camera through the engine handle.

Camera Concepts

  • Center-Based: The camera is defined by the world coordinate at the exact center of the viewport.
  • Infinite Grid: You can pan infinitely in any direction.
  • Zoom: Controls how many pixels a single grid unit occupies (scale).

Initial Position

Set the initial camera position with the center prop:

<CanvasTileEngine
engine={engine}
renderer={new RendererCanvas()}
config={config}
center={{ x: 10, y: 10 }} // Start centered at (10, 10)
>
{/* children */}
</CanvasTileEngine>

Programmatic Control

Access camera methods through the engine handle when engine.isReady is true.

Moving the Camera

goCenter(x, y, duration?)

Smoothly animates the camera to a new position.

function MapWithNavigation() {
const engine = useCanvasTileEngine();

const goToBase = () => {
if (engine.isReady) {
engine.goCenter(0, 0, 1000); // Pan to origin over 1 second
}
};

const goToMarker = () => {
if (engine.isReady) {
engine.goCenter(50, 50, 500); // Pan to (50, 50) over 500ms
}
};

return (
<div>
<button onClick={goToBase}>Go to Base</button>
<button onClick={goToMarker}>Go to Marker</button>

<CanvasTileEngine engine={engine} renderer={new RendererCanvas()} config={config}>
{/* children */}
</CanvasTileEngine>
</div>
);
}

setCenter(center)

Instantly jumps to a position without animation.

const jumpToPosition = (x: number, y: number) => {
if (engine.isReady) {
engine.setCenter({ x, y });
}
};

getCenter()

Returns the current center coordinates of the view.

const logPosition = () => {
if (engine.isReady) {
const center = engine.getCenter();
console.log("Current position:", center); // { x: 5.5, y: 10.2 }
}
};
Renamed APIs

getVisibleBounds()

Returns the world coordinate bounds of the visible viewport. Useful for knowing which cells are on screen.

const placeRandomMine = () => {
if (engine.isReady) {
const bounds = engine.getVisibleBounds();
// { minX: 0, maxX: 10, minY: 0, maxY: 10 }

const x = bounds.minX + Math.floor(Math.random() * (bounds.maxX - bounds.minX));
const y = bounds.minY + Math.floor(Math.random() * (bounds.maxY - bounds.minY));

console.log("Random position:", x, y);
}
};
PropertyDescription
minXLeft edge of viewport (floored)
maxXRight edge of viewport (ceiled)
minYTop edge of viewport (floored)
maxYBottom edge of viewport (ceiled)

Tracking Camera Position

Use onCoordsChange to track camera movement:

function MapWithCoordinateDisplay() {
const engine = useCanvasTileEngine();
const [center, setCenter] = useState({ x: 0, y: 0 });

return (
<div>
<div>
Position: ({center.x.toFixed(1)}, {center.y.toFixed(1)})
</div>

<CanvasTileEngine engine={engine} renderer={new RendererCanvas()} config={config} onCoordsChange={setCenter}>
{/* children */}
</CanvasTileEngine>
</div>
);
}

Zooming

Configure zoom limits in the config:

const config = {
scale: 50, // Initial zoom (pixels per grid unit)
minScale: 10, // Minimum zoom out
maxScale: 200, // Maximum zoom in
eventHandlers: {
zoom: true, // Enable mouse wheel zoom
},
};

zoomIn(factor?)

Zooms in by a given factor (default: 1.5), centered on the viewport.

const handleZoomIn = () => {
engine.zoomIn(); // Zoom in by 1.5x
engine.zoomIn(2); // Zoom in by 2x
};

zoomOut(factor?)

Zooms out by a given factor (default: 1.5), centered on the viewport.

const handleZoomOut = () => {
engine.zoomOut(); // Zoom out by 1.5x
engine.zoomOut(2); // Zoom out by 2x
};

getScale()

Returns the current zoom scale.

const logScale = () => {
const scale = engine.getScale();
console.log("Current scale:", scale); // 50
};

setScale(scale)

Sets the zoom level directly. The value is clamped to minScale and maxScale bounds, and the change is anchored at the viewport center (matching goScale/zoomIn/zoomOut).

ParameterTypeDescription
scalenumberThe desired zoom level (pixels per grid unit).
const setZoom = (scale: number) => {
if (engine.isReady) {
engine.setScale(scale);
}
};

goScale(scale, duration?, onComplete?)

Smoothly animates the zoom level to a target value, like goCenter does for position. The zoom is anchored at the viewport center (matching zoomIn/zoomOut), and the target is clamped to minScale and maxScale bounds.

ParameterTypeDefaultDescription
scalenumberRequiredThe desired zoom level (pixels per grid unit).
durationnumber500Animation duration in ms. 0 = instant.
onCompletefunction-Called when the animation finishes.
const zoomToDetail = () => {
// Smoothly zoom to 100 pixels per grid unit over 1 second
engine.goScale(100, 1000);

// Combine with goCenter for a fly-to effect
engine.goCenter(15, 20, 1000);
};

setScaleLimits(minScale, maxScale)

Updates the minScale and maxScale limits at runtime. All zooming (gestures, setScale, goScale, zoomIn, zoomOut) clamps to the new range, and the current scale is clamped into it immediately (firing onZoom if it changes).

ParameterTypeDescription
minScalenumberNew minimum zoom level. Must be positive.
maxScalenumberNew maximum zoom level. Must be >= minScale.
const allowDeepZoom = () => {
// Allow zooming between 10 and 200 pixels per grid unit
engine.setScaleLimits(10, 200);
};

fitBounds(bounds, options?)

Fits a world-space rectangle into the viewport: centers the view on the rectangle and picks the largest scale that keeps the whole (padded) area visible, clamped to the scale limits. Animated by default. Not related to setBounds, which restricts camera movement.

ParameterTypeDescription
bounds{ minX, maxX, minY, maxY }Rectangle to fit. Every edge must be finite.
options.paddingnumberExtra world-unit margin on every side; scales with the content. Default 0.
options.paddingPxnumberScreen-pixel margin kept free on every side, independent of the content's world size. Wins over padding.
options.durationMsnumberAnimation duration in ms. Default 500; 0 = instant.
options.onCompletefunctionCalled when the fit completes.

padding keeps the margin proportional to the content; paddingPx keeps it a fixed number of screen pixels regardless of how large the fitted area is — the right choice for fit-to-selection UI (same world/px pair as hitTest's padding/paddingPx).

const showWholeBoard = () => {
engine.fitBounds({ minX: 0, maxX: 32, minY: 0, maxY: 32 }, { padding: 1 });
};

const zoomToSelection = (selection: { minX: number; maxX: number; minY: number; maxY: number }) => {
engine.fitBounds(selection, { paddingPx: 24, durationMs: 300 });
};

Example: Zoom Controls

function MapWithZoomControls() {
const engine = useCanvasTileEngine();

return (
<div>
<div>
<button onClick={() => engine.zoomIn()}>+</button>
<button onClick={() => engine.zoomOut()}>-</button>
</div>

<CanvasTileEngine engine={engine} renderer={new RendererCanvas()} config={config}>
{/* children */}
</CanvasTileEngine>
</div>
);
}

Viewport & Resizing

Auto-Resizing

Enable automatic resize handling:

const config = {
// ...
eventHandlers: {
resize: true,
},
};

Manual Resizing

resize(width, height, duration?)

Manually trigger a resize with optional animation:

const changeResolution = (width: number, height: number) => {
if (engine.isReady) {
engine.resize(width, height, 500); // Animate over 500ms
}
};

getSize()

Get the current canvas dimensions:

const logSize = () => {
if (engine.isReady) {
const size = engine.getSize();
console.log("Canvas size:", size); // { width: 1920, height: 1080 }
}
};

Handling Resize Events

<CanvasTileEngine
engine={engine}
renderer={new RendererCanvas()}
config={config}
onResize={() => {
const size = engine.getSize();
console.log("Resized to:", size);
}}
>
{/* children */}
</CanvasTileEngine>

Camera Bounds

Restrict camera movement to a specific area:

In Configuration

const config = {
// ...
bounds: {
minX: -100,
maxX: 100,
minY: -100,
maxY: 100,
},
};

Dynamically with setBounds

const restrictToArea = () => {
if (engine.isReady) {
engine.setBounds({ minX: 0, maxX: 500, minY: 0, maxY: 500 });
}
};

const removeBounds = () => {
if (engine.isReady) {
engine.setBounds({
minX: -Infinity,
maxX: Infinity,
minY: -Infinity,
maxY: Infinity,
});
}
};