- 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`). |
GitHub에서 보기