Resize, DPR & the canvas
Fit the canvas to the window, render crisply on high-DPI displays, and re-lay-out content when the size changes.
Resize, DPR & the canvas
Your first scene rendered into a fixed 800×600 canvas. Real apps run in a window the player can resize, on a phone in portrait or landscape, and on high-DPI (“Retina”) displays where one CSS pixel covers several physical pixels. This chapter covers the three things that turn a fixed demo into something that fills its container and stays sharp.
Fixed vs. responsive
By default the Application uses the canvas.width and canvas.height you pass — a fixed-size surface. That is the right choice for a pixel-art game with a locked resolution, or any layout designed around exact dimensions.
For everything else you want the canvas to track its container. The size you draw against then changes at runtime, so any layout that assumes a fixed center or fixed edges needs to recompute when the size changes.
Crisp rendering with pixelRatio
On a high-DPI display, a canvas sized in CSS pixels is stretched across more physical pixels, so anything drawn at the CSS resolution looks soft. The pixelRatio canvas option fixes this: it scales the canvas backing store up by that factor while the CSS display size stays the same. Set it to the display’s devicePixelRatio for native sharpness:
const app = new Application({
canvas: {
width: 1280,
height: 720,
mount: document.body,
sizingMode: 'fit',
pixelRatio: window.devicePixelRatio || 1,
},
clearColor: Color.black,
loader: {
basePath: 'assets/',
},
}); With pixelRatio: 2, an 800×600 canvas renders into a 1600×1200 backing store but still displays at 800×600 CSS pixels — twice the detail per pixel, so sprites and text stay crisp. At pixelRatio: 1 (the default) the backing store matches the CSS size.
Fitting the window
app.resize(width, height) sets a new logical size: it resizes the canvas (applying pixelRatio to the backing store), resizes the active backend’s render target, and dispatches the onResize signal. Call it once on startup and again whenever the window changes:
// This example demonstrates manual resize handling: the canvas is resized to
// fill the window on every `resize` event. (For a hands-off alternative, enable
// the `autosize` canvas option instead.)
window.addEventListener('resize', () => {
app.resize(window.innerWidth, window.innerHeight);
});
app.resize(window.innerWidth, window.innerHeight); The first call after adding the listener sets the initial size; the listener keeps it in sync as the window changes. The width and height you pass are logical (CSS) pixels — pixelRatio is applied internally, so you never multiply by it yourself.
Re-laying out on size change
Resizing the surface does not move what you already positioned. A sprite centered at (400, 300) for an 800×600 canvas is no longer centered once the canvas grows. Recompute any size-dependent positions whenever the size changes — the example does this from a small layout helper:
private layout(): void {
const { width, height } = this.app.canvas;
const dpr = Math.max(1, window.devicePixelRatio || 1);
this.sprite.setPosition(width / 2, height / 2);
this.info.setPosition(width / 2, 12);
this.info.text = `${width}x${height} @ DPR ${dpr.toFixed(2)}`;
} The example calls layout() every frame from update, which is simple and always correct. For heavier layouts, subscribe to app.onResize instead and recompute only when the size actually changes:
init() {
this.layout();
app.onResize.add(() => this.layout());
}
Either approach works — poll in update for small scenes, react to onResize when re-layout is expensive.
Example
Drag the window edges: the canvas fills the viewport, the sprite stays centered, and the overlay reports the live canvas size and device pixel ratio.
Try it
Playground
API
Where to go next
The next part, Runtime, goes deeper on the Application — how it owns the canvas, the sizing you just used, and the frame loop — then moves on to scenes, the scene graph, and views.