UNPKG

phaser

Version:

A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.

1,395 lines (1,194 loc) 64.3 kB
/** * @author Richard Davey <rich@phaser.io> * @copyright 2013-2025 Phaser Studio Inc. * @license {@link https://opensource.org/licenses/MIT|MIT License} */ var CONST = require('./const'); var Class = require('../utils/Class'); var Clamp = require('../math/Clamp'); var EventEmitter = require('eventemitter3'); var Events = require('./events'); var GameEvents = require('../core/events'); var GetInnerHeight = require('../dom/GetInnerHeight'); var GetTarget = require('../dom/GetTarget'); var GetScreenOrientation = require('../dom/GetScreenOrientation'); var NOOP = require('../utils/NOOP'); var Rectangle = require('../geom/rectangle/Rectangle'); var Size = require('../structs/Size'); var SnapFloor = require('../math/snap/SnapFloor'); var Vector2 = require('../math/Vector2'); var Camera = require('../cameras/2d/Camera'); /** * @classdesc * The Scale Manager handles the scaling, resizing and alignment of the game canvas. * * The way scaling is handled is by setting the game canvas to a fixed size, which is defined in the * game configuration. You also define the parent container in the game config. If no parent is given, * it will default to using the document body. The Scale Manager will then look at the available space * within the _parent_ and scale the canvas accordingly. Scaling is handled by setting the canvas CSS * width and height properties, leaving the width and height of the canvas element itself untouched. * Scaling is therefore achieved by keeping the core canvas the same size and 'stretching' * it via its CSS properties. This gives the same result and speed as using the `transform-scale` CSS * property, without the need for browser prefix handling. * * The calculations for the scale are heavily influenced by the bounding parent size, which is the computed * dimensions of the canvas's parent. The CSS rules of the parent element play an important role in the * operation of the Scale Manager. For example, if the parent has no defined width or height, then actions * like auto-centering will fail to achieve the required result. The Scale Manager works in tandem with the * CSS you set-up on the page hosting your game, rather than taking control of it. * * #### Parent and Display canvas containment guidelines: * * - Style the Parent element (of the game canvas) to control the Parent size and thus the games size and layout. * * - The Parent element's CSS styles should _effectively_ apply maximum (and minimum) bounding behavior. * * - The Parent element should _not_ apply a padding as this is not accounted for. * If a padding is required apply it to the Parent's parent or apply a margin to the Parent. * If you need to add a border, margin or any other CSS around your game container, then use a parent element and * apply the CSS to this instead, otherwise you'll be constantly resizing the shape of the game container. * * - The Display canvas layout CSS styles (i.e. margins, size) should not be altered / specified as * they may be updated by the Scale Manager. * * #### Scale Modes * * The way the scaling is handled is determined by the `scaleMode` property. The default is `NONE`, * which prevents Phaser from scaling or touching the canvas, or its parent, at all. In this mode, you are * responsible for all scaling. The other scaling modes afford you automatic scaling. * * If you wish to scale your game so that it always fits into the available space within the parent, you * should use the scale mode `FIT`. Look at the documentation for other scale modes to see what options are * available. Here is a basic config showing how to set this scale mode: * * ```javascript * scale: { * parent: 'yourgamediv', * mode: Phaser.Scale.FIT, * width: 800, * height: 600 * } * ``` * * Place the `scale` config object within your game config. * * If you wish for the canvas to be resized directly, so that the canvas itself fills the available space * (i.e. it isn't scaled, it's resized) then use the `RESIZE` scale mode. This will give you a 1:1 mapping * of canvas pixels to game size. In this mode CSS isn't used to scale the canvas, it's literally adjusted * to fill all available space within the parent. You should be extremely careful about the size of the * canvas you're creating when doing this, as the larger the area, the more work the GPU has to do and it's * very easy to hit fill-rate limits quickly. * * For complex, custom-scaling requirements, you should probably consider using the `RESIZE` scale mode, * with your own limitations in place re: canvas dimensions and managing the scaling with the game scenes * yourself. For the vast majority of games, however, the `FIT` mode is likely to be the most used. * * Please appreciate that the Scale Manager cannot perform miracles. All it does is scale your game canvas * as best it can, based on what it can infer from its surrounding area. There are all kinds of environments * where it's up to you to guide and help the canvas position itself, especially when built into rendering * frameworks like React and Vue. If your page requires meta tags to prevent user scaling gestures, or such * like, then it's up to you to ensure they are present in the html. * * #### Centering * * You can also have the game canvas automatically centered. Again, this relies heavily on the parent being * properly configured and styled, as the centering offsets are based entirely on the available space * within the parent element. Centering is disabled by default, or can be applied horizontally, vertically, * or both. Here's an example: * * ```javascript * scale: { * parent: 'yourgamediv', * autoCenter: Phaser.Scale.CENTER_BOTH, * width: 800, * height: 600 * } * ``` * * #### Fullscreen API * * If the browser supports it, you can send your game into fullscreen mode. In this mode, the game will fill * the entire display, removing all browser UI and anything else present on the screen. It will remain in this * mode until your game either disables it, or until the user tabs out or presses ESCape if on desktop. It's a * great way to achieve a desktop-game like experience from the browser, but it does require a modern browser * to handle it. Some mobile browsers also support this. * * @class ScaleManager * @memberof Phaser.Scale * @extends Phaser.Events.EventEmitter * @constructor * @since 3.16.0 * * @param {Phaser.Game} game - A reference to the Phaser.Game instance. */ var ScaleManager = new Class({ Extends: EventEmitter, initialize: function ScaleManager (game) { EventEmitter.call(this); /** * A reference to the Phaser.Game instance. * * @name Phaser.Scale.ScaleManager#game * @type {Phaser.Game} * @readonly * @since 3.15.0 */ this.game = game; /** * A reference to the HTML Canvas Element that Phaser uses to render the game. * * @name Phaser.Scale.ScaleManager#canvas * @type {HTMLCanvasElement} * @since 3.16.0 */ this.canvas; /** * The DOM bounds of the canvas element. * * @name Phaser.Scale.ScaleManager#canvasBounds * @type {Phaser.Geom.Rectangle} * @since 3.16.0 */ this.canvasBounds = new Rectangle(); /** * The parent object of the Canvas. Often a div, or the browser window, or nothing in non-browser environments. * * This is set in the Game Config as the `parent` property. If undefined (or just not present), it will default * to use the document body. If specifically set to `null` Phaser will ignore all parent operations. * * @name Phaser.Scale.ScaleManager#parent * @type {?any} * @since 3.16.0 */ this.parent = null; /** * Is the parent element the browser window? * * @name Phaser.Scale.ScaleManager#parentIsWindow * @type {boolean} * @since 3.16.0 */ this.parentIsWindow = false; /** * The Parent Size component. * * @name Phaser.Scale.ScaleManager#parentSize * @type {Phaser.Structs.Size} * @since 3.16.0 */ this.parentSize = new Size(); /** * The Game Size component. * * The un-modified game size, as requested in the game config (the raw width / height), * as used for world bounds, cameras, etc * * @name Phaser.Scale.ScaleManager#gameSize * @type {Phaser.Structs.Size} * @since 3.16.0 */ this.gameSize = new Size(); /** * The Base Size component. * * The modified game size, which is the auto-rounded gameSize, used to set the canvas width and height * (but not the CSS style) * * @name Phaser.Scale.ScaleManager#baseSize * @type {Phaser.Structs.Size} * @since 3.16.0 */ this.baseSize = new Size(); /** * The Display Size component. * * The size used for the canvas style, factoring in the scale mode, parent and other values. * * @name Phaser.Scale.ScaleManager#displaySize * @type {Phaser.Structs.Size} * @since 3.16.0 */ this.displaySize = new Size(); /** * The game scale mode. * * @name Phaser.Scale.ScaleManager#scaleMode * @type {Phaser.Scale.ScaleModeType} * @since 3.16.0 */ this.scaleMode = CONST.SCALE_MODE.NONE; /** * The game zoom factor. * * This value allows you to multiply your games base size by the given zoom factor. * This is then used when calculating the display size, even in `NONE` situations. * If you don't want Phaser to touch the canvas style at all, this value should be 1. * * Can also be set to `MAX_ZOOM` in which case the zoom value will be derived based * on the game size and available space within the parent. * * @name Phaser.Scale.ScaleManager#zoom * @type {number} * @since 3.16.0 */ this.zoom = 1; /** * Internal flag set when the game zoom factor is modified. * * @name Phaser.Scale.ScaleManager#_resetZoom * @type {boolean} * @readonly * @since 3.19.0 */ this._resetZoom = false; /** * The scale factor between the baseSize and the canvasBounds. * * @name Phaser.Scale.ScaleManager#displayScale * @type {Phaser.Math.Vector2} * @since 3.16.0 */ this.displayScale = new Vector2(1, 1); /** * If set, the canvas sizes will be automatically passed through Math.floor. * This results in rounded pixel display values, which is important for performance on legacy * and low powered devices, but at the cost of not achieving a 'perfect' fit in some browser windows. * * @name Phaser.Scale.ScaleManager#autoRound * @type {boolean} * @since 3.16.0 */ this.autoRound = false; /** * Automatically center the canvas within the parent? The different centering modes are: * * 1. No centering. * 2. Center both horizontally and vertically. * 3. Center horizontally. * 4. Center vertically. * * Please be aware that in order to center the game canvas, you must have specified a parent * that has a size set, or the canvas parent is the document.body. * * @name Phaser.Scale.ScaleManager#autoCenter * @type {Phaser.Scale.CenterType} * @since 3.16.0 */ this.autoCenter = CONST.CENTER.NO_CENTER; /** * The current device orientation. * * Orientation events are dispatched via the Device Orientation API, typically only on mobile browsers. * * @name Phaser.Scale.ScaleManager#orientation * @type {Phaser.Scale.OrientationType} * @since 3.16.0 */ this.orientation = CONST.ORIENTATION.LANDSCAPE; /** * A reference to the Device.Fullscreen object. * * @name Phaser.Scale.ScaleManager#fullscreen * @type {Phaser.Device.Fullscreen} * @since 3.16.0 */ this.fullscreen; /** * The DOM Element which is sent into fullscreen mode. * * @name Phaser.Scale.ScaleManager#fullscreenTarget * @type {?any} * @since 3.16.0 */ this.fullscreenTarget = null; /** * Did Phaser create the fullscreen target div, or was it provided in the game config? * * @name Phaser.Scale.ScaleManager#_createdFullscreenTarget * @type {boolean} * @private * @since 3.16.0 */ this._createdFullscreenTarget = false; /** * The dirty state of the Scale Manager. * Set if there is a change between the parent size and the current size. * * @name Phaser.Scale.ScaleManager#dirty * @type {boolean} * @since 3.16.0 */ this.dirty = false; /** * How many milliseconds should elapse before checking if the browser size has changed? * * Most modern browsers dispatch a 'resize' event, which the Scale Manager will listen for. * However, older browsers fail to do this, or do it consistently, so we fall back to a * more traditional 'size check' based on a time interval. You can control how often it is * checked here. * * @name Phaser.Scale.ScaleManager#resizeInterval * @type {number} * @since 3.16.0 */ this.resizeInterval = 500; /** * Internal size interval tracker. * * @name Phaser.Scale.ScaleManager#_lastCheck * @type {number} * @private * @since 3.16.0 */ this._lastCheck = 0; /** * Internal flag to check orientation state. * * @name Phaser.Scale.ScaleManager#_checkOrientation * @type {boolean} * @private * @since 3.16.0 */ this._checkOrientation = false; /** * Internal object containing our defined event listeners. * * @name Phaser.Scale.ScaleManager#domlisteners * @type {object} * @private * @since 3.16.0 */ this.domlisteners = { orientationChange: NOOP, windowResize: NOOP, fullScreenChange: NOOP, fullScreenError: NOOP }; }, /** * Called _before_ the canvas object is created and added to the DOM. * * @method Phaser.Scale.ScaleManager#preBoot * @protected * @listens Phaser.Core.Events#BOOT * @since 3.16.0 */ preBoot: function () { // Parse the config to get the scaling values we need this.parseConfig(this.game.config); this.game.events.once(GameEvents.BOOT, this.boot, this); }, /** * The Boot handler is called by Phaser.Game when it first starts up. * The renderer is available by now and the canvas has been added to the DOM. * * @method Phaser.Scale.ScaleManager#boot * @protected * @fires Phaser.Scale.Events#RESIZE * @since 3.16.0 */ boot: function () { var game = this.game; this.canvas = game.canvas; this.fullscreen = game.device.fullscreen; var scaleMode = this.scaleMode; if (scaleMode !== CONST.SCALE_MODE.RESIZE && scaleMode !== CONST.SCALE_MODE.EXPAND) { this.displaySize.setAspectMode(scaleMode); } if (scaleMode === CONST.SCALE_MODE.NONE) { this.resize(this.width, this.height); } else { this.getParentBounds(); // Only set the parent bounds if the parent has an actual size if (this.parentSize.width > 0 && this.parentSize.height > 0) { this.displaySize.setParent(this.parentSize); } this.refresh(); } game.events.on(GameEvents.PRE_STEP, this.step, this); game.events.once(GameEvents.READY, this.refresh, this); game.events.once(GameEvents.DESTROY, this.destroy, this); this.startListeners(); }, /** * Parses the game configuration to set-up the scale defaults. * * @method Phaser.Scale.ScaleManager#parseConfig * @protected * @since 3.16.0 * * @param {Phaser.Types.Core.GameConfig} config - The Game configuration object. */ parseConfig: function (config) { // Get the parent element, if any this.getParent(config); // Get the size of the parent element // This can often set a height of zero (especially for un-styled divs) this.getParentBounds(); var width = config.width; var height = config.height; var scaleMode = config.scaleMode; var zoom = config.zoom; var autoRound = config.autoRound; // If width = '100%', or similar value if (typeof width === 'string') { // Does width have a % character at the end? If not, we use it as a numeric value. if (width.substr(-1) !== '%') { width = parseInt(width, 10); } else { // If we have a parent with a width, we'll work it out from that var parentWidth = this.parentSize.width; if (parentWidth === 0) { parentWidth = window.innerWidth; } var parentScaleX = parseInt(width, 10) / 100; width = Math.floor(parentWidth * parentScaleX); } } // If height = '100%', or similar value if (typeof height === 'string') { // Does height have a % character at the end? If not, we use it as a numeric value. if (height.substr(-1) !== '%') { height = parseInt(height, 10); } else { // If we have a parent with a height, we'll work it out from that var parentHeight = this.parentSize.height; if (parentHeight === 0) { parentHeight = window.innerHeight; } var parentScaleY = parseInt(height, 10) / 100; height = Math.floor(parentHeight * parentScaleY); } } this.scaleMode = scaleMode; this.autoRound = autoRound; this.autoCenter = config.autoCenter; this.resizeInterval = config.resizeInterval; if (autoRound) { width = Math.floor(width); height = Math.floor(height); } // The un-modified game size, as requested in the game config (the raw width / height) as used for world bounds, etc this.gameSize.setSize(width, height); if (zoom === CONST.ZOOM.MAX_ZOOM) { zoom = this.getMaxZoom(); } this.zoom = zoom; if (zoom !== 1) { this._resetZoom = true; } // The modified game size this.baseSize.setSize(width, height); if (autoRound) { this.baseSize.width = Math.floor(this.baseSize.width); this.baseSize.height = Math.floor(this.baseSize.height); } if (config.minWidth > 0) { this.displaySize.setMin(config.minWidth * zoom, config.minHeight * zoom); } if (config.maxWidth > 0) { this.displaySize.setMax(config.maxWidth * zoom, config.maxHeight * zoom); } // The size used for the canvas style, factoring in the scale mode and parent and zoom value // We just use the w/h here as this is what sets the aspect ratio (which doesn't then change) this.displaySize.setSize(width, height); if (config.snapWidth > 0 || config.snapHeight > 0) { this.displaySize.setSnap(config.snapWidth, config.snapHeight); } this.orientation = GetScreenOrientation(width, height); }, /** * Determines the parent element of the game canvas, if any, based on the game configuration. * * @method Phaser.Scale.ScaleManager#getParent * @since 3.16.0 * * @param {Phaser.Types.Core.GameConfig} config - The Game configuration object. */ getParent: function (config) { var parent = config.parent; if (parent === null) { // User is responsible for managing the parent return; } this.parent = GetTarget(parent); this.parentIsWindow = (this.parent === document.body); if (config.expandParent && config.scaleMode !== CONST.SCALE_MODE.NONE) { var DOMRect = this.parent.getBoundingClientRect(); if (this.parentIsWindow || DOMRect.height === 0) { document.documentElement.style.height = '100%'; document.body.style.height = '100%'; DOMRect = this.parent.getBoundingClientRect(); // The parent STILL has no height, clearly no CSS // has been set on it even though we fixed the body :( if (!this.parentIsWindow && DOMRect.height === 0) { this.parent.style.overflow = 'hidden'; this.parent.style.width = '100%'; this.parent.style.height = '100%'; } } } // And now get the fullscreenTarget if (config.fullscreenTarget && !this.fullscreenTarget) { this.fullscreenTarget = GetTarget(config.fullscreenTarget); } }, /** * Calculates the size of the parent bounds and updates the `parentSize` * properties, only if the canvas has a dom parent. * * @method Phaser.Scale.ScaleManager#getParentBounds * @since 3.16.0 * * @return {boolean} `true` if the parent bounds have changed size or position, otherwise `false`. */ getParentBounds: function () { if (!this.parent) { return false; } var parentSize = this.parentSize; // Ref. http://msdn.microsoft.com/en-us/library/hh781509(v=vs.85).aspx for getBoundingClientRect // The returned value is a DOMRect object which is the smallest rectangle which contains the entire element, // including its padding and border-width. The left, top, right, bottom, x, y, width, and height properties // describe the position and size of the overall rectangle in pixels. Properties other than width and height // are relative to the top-left of the viewport. var DOMRect = this.parent.getBoundingClientRect(); if (this.parentIsWindow && this.game.device.os.iOS) { DOMRect.height = GetInnerHeight(true); } var newWidth = DOMRect.width; var newHeight = DOMRect.height; if (parentSize.width !== newWidth || parentSize.height !== newHeight) { parentSize.setSize(newWidth, newHeight); return true; } else if (this.canvas) { var canvasBounds = this.canvasBounds; var canvasRect = this.canvas.getBoundingClientRect(); if (canvasRect.x !== canvasBounds.x || canvasRect.y !== canvasBounds.y) { return true; } } return false; }, /** * Attempts to lock the orientation of the web browser using the Screen Orientation API. * * This API is only available on modern mobile browsers. * See https://developer.mozilla.org/en-US/docs/Web/API/Screen/lockOrientation for details. * * @method Phaser.Scale.ScaleManager#lockOrientation * @since 3.16.0 * * @param {string} orientation - The orientation you'd like to lock the browser in. Should be an API string such as 'landscape', 'landscape-primary', 'portrait', etc. * * @return {boolean} `true` if the orientation was successfully locked, otherwise `false`. */ lockOrientation: function (orientation) { var lock = screen.lockOrientation || screen.mozLockOrientation || screen.msLockOrientation; if (lock) { return lock.call(screen, orientation); } return false; }, /** * This method will set the size of the Parent Size component, which is used in scaling * and centering calculations. You only need to call this method if you have explicitly * disabled the use of a parent in your game config, but still wish to take advantage of * other Scale Manager features. * * @method Phaser.Scale.ScaleManager#setParentSize * @fires Phaser.Scale.Events#RESIZE * @since 3.16.0 * * @param {number} width - The new width of the parent. * @param {number} height - The new height of the parent. * * @return {this} The Scale Manager instance. */ setParentSize: function (width, height) { this.parentSize.setSize(width, height); return this.refresh(); }, /** * This method will set a new size for your game. * * It should only be used if you're looking to change the base size of your game and are using * one of the Scale Manager scaling modes, i.e. `FIT`. If you're using `NONE` and wish to * change the game and canvas size directly, then please use the `resize` method instead. * * @method Phaser.Scale.ScaleManager#setGameSize * @fires Phaser.Scale.Events#RESIZE * @since 3.16.0 * * @param {number} width - The new width of the game. * @param {number} height - The new height of the game. * * @return {this} The Scale Manager instance. */ setGameSize: function (width, height) { var autoRound = this.autoRound; if (autoRound) { width = Math.floor(width); height = Math.floor(height); } var previousWidth = this.width; var previousHeight = this.height; // The un-modified game size, as requested in the game config (the raw width / height) as used for world bounds, etc this.gameSize.resize(width, height); // The modified game size this.baseSize.resize(width, height); if (autoRound) { this.baseSize.width = Math.floor(this.baseSize.width); this.baseSize.height = Math.floor(this.baseSize.height); } // The size used for the canvas style, factoring in the scale mode and parent and zoom value // Update the aspect ratio this.displaySize.setAspectRatio(width / height); this.canvas.width = this.baseSize.width; this.canvas.height = this.baseSize.height; return this.refresh(previousWidth, previousHeight); }, /** * Call this to modify the size of the Phaser canvas element directly. * You should only use this if you are using the `NONE` scale mode, * it will update all internal components completely. * * If all you want to do is change the size of the parent, see the `setParentSize` method. * * If all you want is to change the base size of the game, but still have the Scale Manager * manage all the scaling (i.e. you're **not** using `NONE`), then see the `setGameSize` method. * * This method will set the `gameSize`, `baseSize` and `displaySize` components to the given * dimensions. It will then resize the canvas width and height to the values given, by * directly setting the properties. Finally, if you have set the Scale Manager zoom value * to anything other than 1 (the default), it will set the canvas CSS width and height to * be the given size multiplied by the zoom factor (the canvas pixel size remains untouched). * * If you have enabled `autoCenter`, it is then passed to the `updateCenter` method and * the margins are set, allowing the canvas to be centered based on its parent element * alone. Finally, the `displayScale` is adjusted and the RESIZE event dispatched. * * @method Phaser.Scale.ScaleManager#resize * @fires Phaser.Scale.Events#RESIZE * @since 3.16.0 * * @param {number} width - The new width of the game. * @param {number} height - The new height of the game. * * @return {this} The Scale Manager instance. */ resize: function (width, height) { var zoom = this.zoom; var autoRound = this.autoRound; if (autoRound) { width = Math.floor(width); height = Math.floor(height); } var previousWidth = this.width; var previousHeight = this.height; // The un-modified game size, as requested in the game config (the raw width / height) as used for world bounds, etc this.gameSize.resize(width, height); // The modified game size this.baseSize.resize(width, height); if (autoRound) { this.baseSize.width = Math.floor(this.baseSize.width); this.baseSize.height = Math.floor(this.baseSize.height); } // The size used for the canvas style, factoring in the scale mode and parent and zoom value // We just use the w/h here as this is what sets the aspect ratio (which doesn't then change) this.displaySize.setSize((width * zoom), (height * zoom)); this.canvas.width = this.baseSize.width; this.canvas.height = this.baseSize.height; var style = this.canvas.style; var styleWidth = width * zoom; var styleHeight = height * zoom; if (autoRound) { styleWidth = Math.floor(styleWidth); styleHeight = Math.floor(styleHeight); } if (styleWidth !== width || styleHeight !== height) { style.width = styleWidth + 'px'; style.height = styleHeight + 'px'; } return this.refresh(previousWidth, previousHeight); }, /** * Sets the zoom value of the Scale Manager. * * @method Phaser.Scale.ScaleManager#setZoom * @fires Phaser.Scale.Events#RESIZE * @since 3.16.0 * * @param {number} value - The new zoom value of the game. * * @return {this} The Scale Manager instance. */ setZoom: function (value) { this.zoom = value; this._resetZoom = true; return this.refresh(); }, /** * Sets the zoom to be the maximum possible based on the _current_ parent size. * * @method Phaser.Scale.ScaleManager#setMaxZoom * @fires Phaser.Scale.Events#RESIZE * @since 3.16.0 * * @return {this} The Scale Manager instance. */ setMaxZoom: function () { this.zoom = this.getMaxZoom(); this._resetZoom = true; return this.refresh(); }, /** * By setting a Snap value, when the browser size is modified, its dimensions will automatically * be snapped to the nearest grid slice, using floor. For example, if you have snap value of 16, * and the width changes to 68, then it will snap down to 64 (the closest multiple of 16 when floored) * * This mode is best used with the `FIT` scale mode. * * Call this method with no arguments to reset the snap values. * * Calling this method automatically invokes `ScaleManager.refresh` which emits a `RESIZE` event. * * @method Phaser.Scale.ScaleManager#setSnap * @fires Phaser.Scale.Events#RESIZE * @since 3.80.0 * * @param {number} [snapWidth=0] - The amount to snap the width to. If you don't want to snap the width, pass a value of zero. * @param {number} [snapHeight=snapWidth] - The amount to snap the height to. If not provided it will use the `snapWidth` value. If you don't want to snap the height, pass a value of zero. * * @return {this} The Scale Manager instance. */ setSnap: function (snapWidth, snapHeight) { if (snapWidth === undefined) { snapWidth = 0; } if (snapHeight === undefined) { snapHeight = snapWidth; } this.displaySize.setSnap(snapWidth, snapHeight); return this.refresh(); }, /** * Refreshes the internal scale values, bounds sizes and orientation checks. * * Once finished, dispatches the resize event. * * This is called automatically by the Scale Manager when the browser window size changes, * as long as it is using a Scale Mode other than 'NONE'. * * @method Phaser.Scale.ScaleManager#refresh * @fires Phaser.Scale.Events#RESIZE * @since 3.16.0 * * @param {number} [previousWidth] - The previous width of the game. Only set if the gameSize has changed. * @param {number} [previousHeight] - The previous height of the game. Only set if the gameSize has changed. * * @return {this} The Scale Manager instance. */ refresh: function (previousWidth, previousHeight) { if (previousWidth === undefined) { previousWidth = this.width; } if (previousHeight === undefined) { previousHeight = this.height; } this.updateScale(); this.updateBounds(); this.updateOrientation(); this.displayScale.set(this.baseSize.width / this.canvasBounds.width, this.baseSize.height / this.canvasBounds.height); var domContainer = this.game.domContainer; if (domContainer) { this.baseSize.setCSS(domContainer); var canvasStyle = this.canvas.style; var domStyle = domContainer.style; domStyle.transform = 'scale(' + this.displaySize.width / this.baseSize.width + ',' + this.displaySize.height / this.baseSize.height + ')'; domStyle.marginLeft = canvasStyle.marginLeft; domStyle.marginTop = canvasStyle.marginTop; } this.emit(Events.RESIZE, this.gameSize, this.baseSize, this.displaySize, previousWidth, previousHeight); return this; }, /** * Internal method that checks the current screen orientation, only if the internal check flag is set. * * If the orientation has changed it updates the orientation property and then dispatches the orientation change event. * * @method Phaser.Scale.ScaleManager#updateOrientation * @fires Phaser.Scale.Events#ORIENTATION_CHANGE * @since 3.16.0 */ updateOrientation: function () { if (this._checkOrientation) { this._checkOrientation = false; var newOrientation = GetScreenOrientation(this.width, this.height); if (newOrientation !== this.orientation) { this.orientation = newOrientation; this.emit(Events.ORIENTATION_CHANGE, newOrientation); } } }, /** * Internal method that manages updating the size components based on the scale mode. * * @method Phaser.Scale.ScaleManager#updateScale * @since 3.16.0 */ updateScale: function () { var style = this.canvas.style; var width = this.gameSize.width; var height = this.gameSize.height; var styleWidth; var styleHeight; var zoom = this.zoom; var autoRound = this.autoRound; if (this.scaleMode === CONST.SCALE_MODE.NONE) { // No scale this.displaySize.setSize((width * zoom), (height * zoom)); styleWidth = this.displaySize.width; styleHeight = this.displaySize.height; if (autoRound) { styleWidth = Math.floor(styleWidth); styleHeight = Math.floor(styleHeight); } if (this._resetZoom) { style.width = styleWidth + 'px'; style.height = styleHeight + 'px'; this._resetZoom = false; } } else if (this.scaleMode === CONST.SCALE_MODE.RESIZE) { // Resize to match parent // This will constrain using min/max this.displaySize.setSize(this.parentSize.width, this.parentSize.height); this.gameSize.setSize(this.displaySize.width, this.displaySize.height); this.baseSize.setSize(this.displaySize.width, this.displaySize.height); styleWidth = this.displaySize.width; styleHeight = this.displaySize.height; if (autoRound) { styleWidth = Math.floor(styleWidth); styleHeight = Math.floor(styleHeight); } this.canvas.width = styleWidth; this.canvas.height = styleHeight; } else if (this.scaleMode === CONST.SCALE_MODE.EXPAND) { // Expand canvas size to fit game size's width or height var baseWidth = this.game.config.width; var baseHeight = this.game.config.height; var windowWidth = this.parentSize.width; var windowHeight = this.parentSize.height; var scaleX = windowWidth / baseWidth; var scaleY = windowHeight / baseHeight; var canvasWidth; var canvasHeight; if (scaleX < scaleY) { canvasWidth = baseWidth; canvasHeight = (scaleX !== 0)? windowHeight / scaleX : baseHeight; } else { canvasWidth = (scaleY !== 0)? windowWidth / scaleY : baseWidth; canvasHeight = baseHeight; } var clampedCanvasWidth = Clamp(canvasWidth, this.displaySize.minWidth, this.displaySize.maxWidth); var clampedCanvasHeight = Clamp(canvasHeight, this.displaySize.minHeight, this.displaySize.maxHeight); this.baseSize.setSize(clampedCanvasWidth, clampedCanvasHeight); this.gameSize.setSize(clampedCanvasWidth, clampedCanvasHeight); if (autoRound) { clampedCanvasWidth = Math.floor(clampedCanvasWidth); clampedCanvasHeight = Math.floor(clampedCanvasHeight); } this.canvas.width = clampedCanvasWidth; this.canvas.height = clampedCanvasHeight; // Resize to match parent, like RESIZE mode var clampedWindowWidth = windowWidth * (clampedCanvasWidth / canvasWidth); var clampedWindowHeight = windowHeight * (clampedCanvasHeight / canvasHeight); this.displaySize.setSize(clampedWindowWidth, clampedWindowHeight); styleWidth = this.displaySize.width; styleHeight = this.displaySize.height; if (autoRound) { styleWidth = Math.floor(styleWidth); styleHeight = Math.floor(styleHeight); } style.width = styleWidth + 'px'; style.height = styleHeight + 'px'; } else { // All other scale modes this.displaySize.setSize(this.parentSize.width, this.parentSize.height); styleWidth = this.displaySize.width; styleHeight = this.displaySize.height; if (autoRound) { styleWidth = Math.floor(styleWidth); styleHeight = Math.floor(styleHeight); } style.width = styleWidth + 'px'; style.height = styleHeight + 'px'; } // Update the parentSize in case the canvas / style change modified it this.getParentBounds(); // Finally, update the centering this.updateCenter(); }, /** * Calculates and returns the largest possible zoom factor, based on the current * parent and game sizes. If the parent has no dimensions (i.e. an unstyled div), * or is smaller than the un-zoomed game, then this will return a value of 1 (no zoom) * * @method Phaser.Scale.ScaleManager#getMaxZoom * @since 3.16.0 * * @return {number} The maximum possible zoom factor. At a minimum this value is always at least 1. */ getMaxZoom: function () { var zoomH = SnapFloor(this.parentSize.width, this.gameSize.width, 0, true); var zoomV = SnapFloor(this.parentSize.height, this.gameSize.height, 0, true); return Math.max(Math.min(zoomH, zoomV), 1); }, /** * Calculates and updates the canvas CSS style in order to center it within the * bounds of its parent. If you have explicitly set parent to be `null` in your * game config then this method will likely give incorrect results unless you have called the * `setParentSize` method first. * * It works by modifying the canvas CSS `marginLeft` and `marginTop` properties. * * If they have already been set by your own style sheet, or code, this will overwrite them. * * To prevent the Scale Manager from centering the canvas, either do not set the * `autoCenter` property in your game config, or make sure it is set to `NO_CENTER`. * * @method Phaser.Scale.ScaleManager#updateCenter * @since 3.16.0 */ updateCenter: function () { var autoCenter = this.autoCenter; if (autoCenter === CONST.CENTER.NO_CENTER) { return; } var canvas = this.canvas; var style = canvas.style; var bounds = canvas.getBoundingClientRect(); var width = bounds.width; var height = bounds.height; var offsetX = Math.floor((this.parentSize.width - width) / 2); var offsetY = Math.floor((this.parentSize.height - height) / 2); if (autoCenter === CONST.CENTER.CENTER_HORIZONTALLY) { offsetY = 0; } else if (autoCenter === CONST.CENTER.CENTER_VERTICALLY) { offsetX = 0; } style.marginLeft = offsetX + 'px'; style.marginTop = offsetY + 'px'; }, /** * Updates the `canvasBounds` rectangle to match the bounding client rectangle of the * canvas element being used to track input events. * * @method Phaser.Scale.ScaleManager#updateBounds * @since 3.16.0 */ updateBounds: function () { var bounds = this.canvasBounds; var clientRect = this.canvas.getBoundingClientRect(); bounds.x = clientRect.left + (window.pageXOffset || 0) - (document.documentElement.clientLeft || 0); bounds.y = clientRect.top + (window.pageYOffset || 0) - (document.documentElement.clientTop || 0); bounds.width = clientRect.width; bounds.height = clientRect.height; }, /** * Transforms the pageX value into the scaled coordinate space of the Scale Manager. * * @method Phaser.Scale.ScaleManager#transformX * @since 3.16.0 * * @param {number} pageX - The DOM pageX value. * * @return {number} The translated value. */ transformX: function (pageX) { return (pageX - this.canvasBounds.left) * this.displayScale.x; }, /** * Transforms the pageY value into the scaled coordinate space of the Scale Manager. * * @method Phaser.Scale.ScaleManager#transformY * @since 3.16.0 * * @param {number} pageY - The DOM pageY value. * * @return {number} The translated value. */ transformY: function (pageY) { return (pageY - this.canvasBounds.top) * this.displayScale.y; }, /** * Sends a request to the browser to ask it to go in to full screen mode, using the {@link https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API Fullscreen API}. * * If the browser does not support this, a `FULLSCREEN_UNSUPPORTED` event will be emitted. * * This method _must_ be called from a `pointerup` user-input gesture (**not** `pointerdown`). You cannot launch * games fullscreen without this, as most browsers block it. Games within an iframe will also be blocked * from fullscreen unless the iframe has the `allowfullscreen` attribute. * * On touch devices, such as Android and iOS Safari, you should always use `pointerup` and NOT `pointerdown`, * otherwise the request will fail unless the document in which your game is embedded has already received * some form of touch input, which you cannot guarantee. Activating fullscreen via `pointerup` circumvents * this issue. * * Performing an action that navigates to another page, or opens another tab, will automatically cancel * fullscreen mode, as will the user pressing the ESC key. To cancel fullscreen mode directly from your game, * i.e. by clicking an icon, call the `stopFullscreen` method. * * A browser can only send one DOM element into fullscreen. You can control which element this is by * setting the `fullscreenTarget` property in your game config, or changing the property in the Scale Manager. * Note that the game canvas _must_ be a child of the target. If you do not give a target, Phaser will * automatically create a blank `<div>` element and move the canvas into it, before going fullscreen. * When it leaves fullscreen, the div will be removed. * * @method Phaser.Scale.ScaleManager#startFullscreen * @fires Phaser.Scale.Events#ENTER_FULLSCREEN * @fires Phaser.Scale.Events#FULLSCREEN_FAILED * @fires Phaser.Scale.Events#FULLSCREEN_UNSUPPORTED * @fires Phaser.Scale.Events#RESIZE * @since 3.16.0 * * @param {object} [fullscreenOptions] - The FullscreenOptions dictionary is used to provide configuration options when entering full screen. */ startFullscreen: function (fullscreenOptions) { if (fullscreenOptions === undefined) { fullscreenOptions = { navigationUI: 'hide' }; } var fullscreen = this.fullscreen; if (!fullscreen.available) { this.emit(Events.FULLSCREEN_UNSUPPORTED); return; } if (!fullscreen.active) { var fsTarget = this.getFullscreenTarget(); if (fullscreen.keyboard) { fsTarget[fullscreen.request](Element.ALLOW_KEYBOARD_INPUT); } else { fsTarget[fullscreen.request](fullscreenOptions); } } }, /** * The browser has successfully entered fullscreen mode. * * @method Phaser.Scale.ScaleManager#fullscreenSuccessHandler * @private * @fires Phaser.Scale.Events#ENTER_FULLSCREEN * @fires Phaser.Scale.Events#RESIZE * @since 3.17.0 */ fullscreenSuccessHandler: function () { this.getParentBounds(); this.refresh(); this.emit(Events.ENTER_FULLSCREEN); }, /** * The browser failed to enter fullscreen mode. * * @method Phaser.Scale.ScaleManager#fullscreenErrorHandler * @private * @fires Phaser.Scale.Events#FULLSCREEN_FAILED * @fires Phaser.Scale.Events#RESIZE * @since 3.17.0 * * @param {any} error - The DOM error event. */ fullscreenErrorHandler: function (error) { this.removeFullscreenTarget(); this.emit(Events.FULLSCREEN_FAILED, error); }, /** * An internal method that gets the target element that is used when entering fullscreen mode. * * @method Phaser.Scale.ScaleManager#getFullscreenTarget * @since 3.16.0 * * @return {object} The fullscreen target element. */