phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
372 lines (286 loc) • 18.6 kB
Markdown
---
name: scale-and-responsive
description: "Use this skill when making a Phaser 4 game responsive or handling display scaling. Covers ScaleManager, scale modes (FIT, RESIZE, EXPAND, ENVELOP), auto-center, fullscreen, and browser resize handling. Triggers on: ScaleManager, responsive, resize, fullscreen, FIT, scale mode."
---
# Scale and Responsive Design
> How to use the ScaleManager for scaling, centering, fullscreen, orientation handling, and responsive resize in Phaser 4.
**Key source paths:** `src/scale/ScaleManager.js`, `src/scale/const/`, `src/scale/events/`, `src/core/typedefs/ScaleConfig.js`
**Related skills:** ../game-setup-and-config/SKILL.md
## Quick Start
Scale-to-fit with centering -- the most common setup for responsive games:
```js
const config = {
type: Phaser.AUTO,
scale: {
parent: 'game-container',
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
width: 800,
height: 600
},
scene: MyScene
};
const game = new Phaser.Game(config);
```
The `scale` config object is parsed into a `Phaser.Core.Config` instance which the `ScaleManager` reads during boot. The ScaleManager sets the canvas element size and applies CSS scaling to fit it within its parent.
## Core Concepts
### ScaleManager (src/scale/ScaleManager.js)
The ScaleManager is created during the Game boot sequence and is accessible at `game.scale` or `this.scale` from within a Scene. It extends `EventEmitter`.
**Three internal Size components drive all calculations:**
| Component | Property | Purpose |
|-----------|----------|---------|
| `gameSize` | `game.scale.gameSize` | The unmodified game dimensions from config. Used for world bounds, cameras. Read via `game.scale.width` / `game.scale.height`. |
| `baseSize` | `game.scale.baseSize` | The auto-rounded gameSize. Sets the actual `canvas.width` and `canvas.height` attributes. |
| `displaySize` | `game.scale.displaySize` | The CSS-scaled canvas size after applying scale mode, parent bounds, and zoom. Sets `canvas.style.width` / `canvas.style.height`. |
Scaling works by keeping the canvas element dimensions fixed (baseSize) and stretching it via CSS properties (displaySize). This is equivalent to CSS `transform-scale` but without browser prefix issues.
The `displayScale` property (`Phaser.Math.Vector2`) holds the ratio `baseSize / canvasBounds` and is used internally for input coordinate transformation.
### Scale Modes (src/scale/const/SCALE_MODE_CONST.js)
All modes are on `Phaser.Scale.ScaleModes` and are set via `scale.mode` in config:
| Constant | Value | Behavior |
|----------|-------|----------|
| `NONE` | 0 | No automatic scaling. Canvas uses config width/height. You manage sizing yourself. If you resize the canvas externally, call `game.scale.resize(w, h)` to update internals. |
| `WIDTH_CONTROLS_HEIGHT` | 1 | Height adjusts automatically to maintain aspect ratio based on width. |
| `HEIGHT_CONTROLS_WIDTH` | 2 | Width adjusts automatically to maintain aspect ratio based on height. |
| `FIT` | 3 | Scales to fit inside parent while preserving aspect ratio. May leave empty space (letterbox/pillarbox). Most commonly used mode. |
| `ENVELOP` | 4 | Scales to cover the entire parent while preserving aspect ratio. May extend beyond parent bounds (content gets cropped). |
| `RESIZE` | 5 | Canvas element itself is resized to fill parent. No CSS scaling -- 1:1 pixel mapping. The `gameSize`, `baseSize`, and `displaySize` all change to match parent. Beware of GPU fill-rate on large displays. |
| `EXPAND` | 6 | Hybrid of RESIZE and FIT. The visible area resizes to fill the parent, and the canvas scales to fit inside that area. Added in v3.80. |
**Shorthand constants** are also available directly on `Phaser.Scale`: `Phaser.Scale.FIT`, `Phaser.Scale.RESIZE`, etc.
### Center Modes (src/scale/const/CENTER_CONST.js)
Set via `scale.autoCenter` in config. Centering is achieved by setting CSS `marginLeft` and `marginTop` on the canvas:
| Constant | Value | Behavior |
|----------|-------|----------|
| `NO_CENTER` | 0 | No auto-centering (default). |
| `CENTER_BOTH` | 1 | Center horizontally and vertically within parent. |
| `CENTER_HORIZONTALLY` | 2 | Center horizontally only. |
| `CENTER_VERTICALLY` | 3 | Center vertically only. |
The parent element must have calculable bounds. If the parent has no defined width/height, centering will not work correctly.
### Zoom (src/scale/const/ZOOM_CONST.js)
Set via `scale.zoom` in config. Multiplies the display size:
| Constant | Value | Behavior |
|----------|-------|----------|
| `NO_ZOOM` | 1 | No zoom (default). |
| `ZOOM_2X` | 2 | 2x zoom -- good for pixel art at low base resolution. |
| `ZOOM_4X` | 4 | 4x zoom. |
| `MAX_ZOOM` | -1 | Automatically calculates the largest integer zoom that fits in the parent. |
You can also pass any numeric value. The zoom affects CSS display size but not the canvas resolution.
### Orientation (src/scale/const/ORIENTATION_CONST.js)
Read via `game.scale.orientation`. Values are strings:
- `Phaser.Scale.Orientation.LANDSCAPE` = `'landscape-primary'`
- `Phaser.Scale.Orientation.LANDSCAPE_SECONDARY` = `'landscape-secondary'`
- `Phaser.Scale.Orientation.PORTRAIT` = `'portrait-primary'`
- `Phaser.Scale.Orientation.PORTRAIT_SECONDARY` = `'portrait-secondary'`
Convenience booleans: `game.scale.isPortrait`, `game.scale.isLandscape` (device orientation), `game.scale.isGamePortrait`, `game.scale.isGameLandscape` (game dimensions).
## Common Patterns
### FIT Mode with Centering (Most Common)
```js
scale: {
parent: 'game-container',
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
width: 1280,
height: 720
}
```
The game maintains its 16:9 aspect ratio and centers within the parent. Empty space appears as letterbox/pillarbox bars. Style the parent's background color to control bar appearance.
### Responsive Resize (Dynamic Canvas Size)
```js
scale: {
parent: 'game-container',
mode: Phaser.Scale.RESIZE,
width: '100%',
height: '100%'
}
```
```js
// In your scene -- respond to size changes:
create() {
this.scale.on('resize', this.handleResize, this);
}
handleResize(gameSize, baseSize, displaySize) {
this.cameras.resize(gameSize.width, gameSize.height);
// Reposition UI elements based on new dimensions
}
```
Width/height accept percentage strings (e.g., `'100%'`) which resolve against the parent size. If the parent has no size, they fall back to `window.innerWidth` / `window.innerHeight`.
### Fullscreen Toggle
```js
// Must be called from a pointerup gesture (not pointerdown)
this.input.on('pointerup', () => {
if (this.scale.isFullscreen) {
this.scale.stopFullscreen();
} else {
this.scale.startFullscreen();
}
});
// Or use the convenience method:
this.input.on('pointerup', () => {
this.scale.toggleFullscreen();
});
```
- `startFullscreen(fullscreenOptions)` -- requests browser fullscreen. Default options: `{ navigationUI: 'hide' }`.
- `stopFullscreen()` -- exits fullscreen mode.
- `toggleFullscreen(fullscreenOptions)` -- toggles between the two.
- `isFullscreen` -- read-only boolean for current state.
If no `fullscreenTarget` is configured, Phaser creates a temporary `<div>`, moves the canvas into it, and sends that div fullscreen. The div is removed when leaving fullscreen.
For iframes, the `allowfullscreen` attribute is required.
### Mobile Orientation Handling
```js
create() {
this.scale.on('orientationchange', (orientation) => {
if (orientation === Phaser.Scale.LANDSCAPE) {
// Show game UI
} else {
// Show "rotate device" message
}
});
// Lock orientation (mobile browsers only, limited support):
this.scale.lockOrientation('landscape');
}
```
### Fixed Size (No Scaling)
```js
scale: {
mode: Phaser.Scale.NONE,
width: 800,
height: 600
}
```
In NONE mode, if you change the canvas size externally you must call `game.scale.resize(newWidth, newHeight)` to update all internal components including input coordinates.
### Pixel Art with Max Zoom
```js
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
width: 320,
height: 240,
zoom: Phaser.Scale.MAX_ZOOM
},
pixelArt: true
```
`MAX_ZOOM` calculates the largest integer multiplier that fits in the parent. Combined with `pixelArt: true` (which disables anti-aliasing), this gives crisp pixel rendering.
### Snap Values for Grid Alignment
```js
scale: {
mode: Phaser.Scale.FIT,
width: 800,
height: 600,
snap: { width: 16, height: 16 }
}
```
When the browser resizes, dimensions snap down to the nearest grid multiple (using floor). Best used with FIT mode. Can also be set at runtime: `game.scale.setSnap(16, 16)` or reset with `game.scale.setSnap()`.
### Min/Max Size Constraints
```js
scale: {
mode: Phaser.Scale.FIT,
width: 800,
height: 600,
min: { width: 400, height: 300 },
max: { width: 1600, height: 1200 }
}
```
The display size is clamped to these bounds during scaling calculations.
## Configuration Reference
### ScaleConfig (src/core/typedefs/ScaleConfig.js)
All properties go inside the `scale` object in your game config:
| Property | Type | Default | Description |
|----------|------|---------|-------------|
| `width` | `number\|string` | `1024` | Base game width. Strings like `'100%'` resolve against parent size. |
| `height` | `number\|string` | `768` | Base game height. Strings like `'100%'` resolve against parent size. |
| `zoom` | `number\|ZoomType` | `1` | Canvas zoom factor. Use `Phaser.Scale.MAX_ZOOM` for auto. |
| `parent` | `HTMLElement\|string\|null` | `undefined` | Parent DOM element or its ID. `undefined` = `document.body`. `null` = no parent (you manage canvas placement). |
| `expandParent` | `boolean` | `true` | Allow ScaleManager to set parent/body CSS height to 100%. |
| `mode` | `ScaleModeType` | `NONE` | Scale mode constant (see table above). |
| `min` | `{width, height}` | - | Minimum canvas dimensions. |
| `max` | `{width, height}` | - | Maximum canvas dimensions. |
| `snap` | `{width, height}` | - | Snap values for resize rounding. |
| `autoRound` | `boolean` | `false` | Floor display/style sizes for low-powered device performance. |
| `autoCenter` | `CenterType` | `NO_CENTER` | Auto-centering mode (see table above). |
| `resizeInterval` | `number` | `500` | Milliseconds between parent size checks (fallback polling). |
| `fullscreenTarget` | `HTMLElement\|string` | `undefined` | Element to send fullscreen. If not set, Phaser creates a wrapper div. |
## Events (src/scale/events/)
All events are on `Phaser.Scale.Events` and are emitted by the ScaleManager instance (`game.scale`):
| Event | String Value | Callback Signature | When |
|-------|-------------|-------------------|------|
| `RESIZE` | `'resize'` | `(gameSize, baseSize, displaySize, previousWidth, previousHeight)` | Any resize, refresh, or scale change. The three Size parameters have `.width`, `.height`, `.aspectRatio`. |
| `ORIENTATION_CHANGE` | `'orientationchange'` | `(orientation)` | Device orientation changes. `orientation` is a string constant from `Phaser.Scale.Orientation`. |
| `ENTER_FULLSCREEN` | `'enterfullscreen'` | `()` | Browser successfully enters fullscreen. |
| `LEAVE_FULLSCREEN` | `'leavefullscreen'` | `()` | Browser leaves fullscreen (via code or user ESC). |
| `FULLSCREEN_FAILED` | `'fullscreenfailed'` | `(error)` | Fullscreen request was denied by the browser. |
| `FULLSCREEN_UNSUPPORTED` | `'fullscreenunsupported'` | `()` | Browser does not support the Fullscreen API. |
## API Quick Reference
### Key Properties (read-only unless noted)
| Property | Type | Description |
|----------|------|-------------|
| `width` | `number` | Game width (from `gameSize.width`). |
| `height` | `number` | Game height (from `gameSize.height`). |
| `isFullscreen` | `boolean` | Currently in fullscreen mode. |
| `isPortrait` | `boolean` | Device is in portrait orientation. |
| `isLandscape` | `boolean` | Device is in landscape orientation. |
| `isGamePortrait` | `boolean` | Game dimensions are taller than wide. |
| `isGameLandscape` | `boolean` | Game dimensions are wider than tall. |
| `scaleMode` | `number` | Current scale mode constant. |
| `orientation` | `string` | Current orientation string. |
| `zoom` | `number` | Current zoom factor. |
| `autoRound` | `boolean` | Whether sizes are auto-floored. |
| `autoCenter` | `number` | Current center mode constant. |
| `parentSize` | `Size` | Computed parent dimensions. |
| `gameSize` | `Size` | Unmodified game dimensions. |
| `baseSize` | `Size` | Canvas element dimensions. |
| `displaySize` | `Size` | CSS-scaled display dimensions. |
| `displayScale` | `Vector2` | Ratio of baseSize to canvasBounds (used for input mapping). |
| `canvas` | `HTMLCanvasElement` | The game canvas element. |
| `canvasBounds` | `Rectangle` | DOM bounding rect of the canvas. |
| `parent` | `HTMLElement` | The parent DOM element. |
| `parentIsWindow` | `boolean` | True if parent is `document.body`. |
| `resizeInterval` | `number` | Polling interval in ms (writable). |
### Key Methods
| Method | Returns | Description |
|--------|---------|-------------|
| `resize(width, height)` | `this` | For NONE mode: sets game, base, and display sizes directly. Updates canvas element and CSS. |
| `setGameSize(width, height)` | `this` | For scaled modes (FIT, etc.): changes the base game size and re-applies scaling. |
| `setParentSize(width, height)` | `this` | Manually set parent dimensions (useful when `parent: null`). |
| `setZoom(value)` | `this` | Change zoom factor at runtime. |
| `setMaxZoom()` | `this` | Set zoom to maximum integer that fits parent. |
| `setSnap(snapWidth, snapHeight)` | `this` | Set or reset snap grid values. |
| `getMaxZoom()` | `number` | Calculate (but don't apply) the max zoom. |
| `getViewPort(camera?, out?)` | `Rectangle` | Get the visible area rectangle, optionally for a specific camera. |
| `refresh(prevW?, prevH?)` | `this` | Force recalculation of scale, bounds, orientation. Emits RESIZE. |
| `startFullscreen(options?)` | `void` | Enter fullscreen. Must be called from `pointerup`. |
| `stopFullscreen()` | `void` | Exit fullscreen. |
| `toggleFullscreen(options?)` | `void` | Toggle fullscreen state. |
| `lockOrientation(orientation)` | `boolean` | Attempt to lock screen orientation (mobile only). |
| `transformX(pageX)` | `number` | Convert DOM pageX to game coordinate. |
| `transformY(pageY)` | `number` | Convert DOM pageY to game coordinate. |
| `getParentBounds()` | `boolean` | Recalculate parent size. Returns true if changed. |
| `updateCenter()` | `void` | Recalculate and apply centering margins. |
| `updateBounds()` | `void` | Update `canvasBounds` from the canvas bounding client rect. |
## Gotchas
1. **Parent element must have dimensions.** The ScaleManager relies on the parent's computed CSS size. An unstyled `<div>` has zero height, which breaks centering and scaling. Either give it explicit CSS dimensions or let `expandParent: true` (the default) set body/parent to 100% height.
2. **No padding on the parent.** The ScaleManager does not account for parent padding. Apply padding to a wrapper element instead, or use margins on the parent's parent.
3. **Do not style the canvas directly.** The ScaleManager controls `canvas.style.width`, `canvas.style.height`, `marginLeft`, and `marginTop`. External CSS on the canvas will conflict.
4. **Fullscreen requires `pointerup`, not `pointerdown`.** On touch devices, `pointerdown` fullscreen requests are blocked unless the document already received touch input. Always use `pointerup` for reliable behavior.
5. **Fullscreen in iframes needs `allowfullscreen`.** Without this attribute on the iframe element, fullscreen requests will silently fail.
6. **RESIZE mode and GPU fill-rate.** RESIZE creates a 1:1 pixel canvas matching the parent. On high-resolution displays this can be very large. Monitor performance on low-end devices.
7. **`resize()` vs `setGameSize()`.** Use `resize()` only in NONE mode (it sets everything directly). Use `setGameSize()` when using FIT/ENVELOP/etc. (it preserves the scale mode calculations).
8. **iOS height quirks.** When the parent is the window on iOS, the ScaleManager uses `GetInnerHeight` to work around Safari's dynamic toolbar affecting `getBoundingClientRect`.
9. **Percentage strings require a sized parent.** Setting `width: '100%'` or `height: '100%'` resolves against `parentSize`. If the parent has no size, it falls back to `window.innerWidth`/`innerHeight`.
10. **`resizeInterval` is a fallback poll.** Modern browsers dispatch `resize` events, but the ScaleManager also polls every `resizeInterval` ms (default 500) to catch edge cases on older browsers.
## Source File Map
| File | Purpose |
|------|---------|
| `src/scale/ScaleManager.js` | Main class -- all scaling, centering, fullscreen, orientation logic. Access via `game.scale`. |
| `src/scale/const/SCALE_MODE_CONST.js` | Scale mode constants: NONE, WIDTH_CONTROLS_HEIGHT, HEIGHT_CONTROLS_WIDTH, FIT, ENVELOP, RESIZE, EXPAND. |
| `src/scale/const/CENTER_CONST.js` | Center mode constants: NO_CENTER, CENTER_BOTH, CENTER_HORIZONTALLY, CENTER_VERTICALLY. |
| `src/scale/const/ZOOM_CONST.js` | Zoom constants: NO_ZOOM, ZOOM_2X, ZOOM_4X, MAX_ZOOM. |
| `src/scale/const/ORIENTATION_CONST.js` | Orientation string constants: LANDSCAPE, LANDSCAPE_SECONDARY, PORTRAIT, PORTRAIT_SECONDARY. |
| `src/scale/events/RESIZE_EVENT.js` | Resize event definition. Callback receives gameSize, baseSize, displaySize, previousWidth, previousHeight. |
| `src/scale/events/ENTER_FULLSCREEN_EVENT.js` | Emitted on successful fullscreen entry. |
| `src/scale/events/LEAVE_FULLSCREEN_EVENT.js` | Emitted when leaving fullscreen. |
| `src/scale/events/FULLSCREEN_FAILED_EVENT.js` | Emitted when fullscreen request is denied. |
| `src/scale/events/FULLSCREEN_UNSUPPORTED_EVENT.js` | Emitted when browser lacks Fullscreen API support. |
| `src/scale/events/ORIENTATION_CHANGE_EVENT.js` | Emitted on device orientation change. Callback receives orientation string. |
| `src/core/typedefs/ScaleConfig.js` | JSDoc typedef for the `scale` config object. |
| `src/structs/Size.js` | The Size component class used by gameSize, baseSize, displaySize. |
| `src/dom/GetInnerHeight.js` | iOS-specific workaround for getting accurate viewport height. |
| `src/dom/GetScreenOrientation.js` | Determines current screen orientation from dimensions. |