How to “lock” orientation in your HTML5 responsive game using Phaser

When you design an HTML5 game to be played on smartphones, unfortunately you can’t lock the orientation, so if the players are playing your game with the wrong orientation, you have to tell them to change orientation. This is quite easy, until you are making a responsive game which uses every single pixel of your device browser window.

The following example, made with Phaser, will show you how to “lock” orientation in a portrait responsive HTML5 game.

Let’s start from the index:

<!DOCTYPE html>
<html>
	<head>
		<title>My responsive Portrait</title>
		<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, minimal-ui" />	
		<script src="phaser.min.js"></script>
     	<script src = "game.js"></script>
     	<style>
			body{
				margin:0px;
				padding:0px;
			}
			#turn{
				width:100%;
				height:100%;
				position:fixed;
				top:0px;
				left:0px;
				background-color:white;
				background-image:url('playportrait.png');
				background-repeat:no-repeat;
				background-position: center center;
				display:none;
			}
		</style>
	</head>
	<body>
		<div id = "turn"></div>
	</body>
</html>

Apart from viewport meta which is used on iOS devices, there’s a div whose id is turn which will be used to display a white background covering everything on the screen, with this image in the center:

This is what we are going to show when the player tries to play the game in portrait mode.

Back to the game, we said we are going to make it responsive, so we will use every available pixel of the browser.

Here is the source code of game.js:

window.onload = function() {
	var gameRatio = window.innerWidth/window.innerHeight;		
	var game = new Phaser.Game(Math.ceil(640*gameRatio), 640, Phaser.CANVAS);
	
	var play = function(game){}  
	
	play.prototype = {
		preload:function(){
			game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
			game.load.image("topleft", "topleft.png");
			game.load.image("center", "center.png");
			game.load.image("bottomright", "bottomright.png");
		},
		create:function(){
			game.add.sprite(0,0,"topleft");
			game.add.sprite(game.width/2,game.height/2,"center").anchor.set(0.5,0.5);	
			game.add.sprite(game.width,game.height,"bottomright").anchor.set(1,1);		
		}
	}
	
	game.state.add("Play",play);
	game.state.start("Play");
}

The script is really simple, as I only place three images, one at the top left of the stage, one at the center and one at the bottom right of the stage. Also look at how I am defining game size: assuming my game is optimized for 640 pixels height, I set the width according to screen ratio.

When you start the game in portrait mode everything looks fine, until you turn the telephone in landscape:

But it’s not a problem since we are going to show the “turn your device” image.

window.onload = function() {
	var gameRatio = window.innerWidth/window.innerHeight;		
	var game = new Phaser.Game(Math.ceil(640*gameRatio), 640, Phaser.CANVAS);
	
	var play = function(game){}  
	
	play.prototype = {
		preload:function(){
			game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
			game.scale.forceOrientation(false, true);
			game.scale.enterIncorrectOrientation.add(handleIncorrect);
            	game.scale.leaveIncorrectOrientation.add(handleCorrect);
			game.load.image("topleft", "topleft.png");
			game.load.image("center", "center.png");
			game.load.image("bottomright", "bottomright.png");
		},
		create:function(){
			game.add.sprite(0,0,"topleft");
			game.add.sprite(game.width/2,game.height/2,"center").anchor.set(0.5,0.5);	
			game.add.sprite(game.width,game.height,"bottomright").anchor.set(1,1);		
		}
	}
	
	function handleIncorrect(){
     	if(!game.device.desktop){
     		document.getElementById("turn").style.display="block";
     	}
	}
	
	function handleCorrect(){
		if(!game.device.desktop){
			document.getElementById("turn").style.display="none";
		}
	}
	
	game.state.add("Play",play);
	game.state.start("Play");
}

With forceOrientation (line 10) we say we want not to be able to play in landscape (the first false argument) but only in portrait (the second true argument).

Then at lines 11 and 12 we listen to enter correct or incorrect orientation calling respectively handleIncorrect and handleCorrect functions.

These function, at lines 24-28 and 30-34 simply show or hide “turn” element setting its CSS display property to “block” (visible) or “none” (invisible).

And that’s what happens when you play in portrait then go to landscape:

Everything seems to be fine, until you start playing in landscape, being told to rotate your device:

The game starts correctly telling you to rotate the device, but unfortunately game dimensions have already been set when you rotate the device, so you will find a tiny landscape game in your portrait device.

Let’s make another change to the code:

window.onload = function() {
	var gameRatio = window.innerWidth/window.innerHeight;		
	var game = new Phaser.Game(Math.ceil(640*gameRatio), 640, Phaser.CANVAS);
	var firstRunLandscape;
	
	var play = function(game){}  
	
	play.prototype = {
		preload:function(){
			firstRunLandscape = game.scale.isGameLandscape;
			game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
			game.scale.forceOrientation(false, true);
			game.scale.enterIncorrectOrientation.add(handleIncorrect);
            	game.scale.leaveIncorrectOrientation.add(handleCorrect);
			game.load.image("topleft", "topleft.png");
			game.load.image("center", "center.png");
			game.load.image("bottomright", "bottomright.png");
		},
		create:function(){
			game.add.sprite(0,0,"topleft");
			game.add.sprite(game.width/2,game.height/2,"center").anchor.set(0.5,0.5);	
			game.add.sprite(game.width,game.height,"bottomright").anchor.set(1,1);		
		}
	}
	
	function handleIncorrect(){
     	if(!game.device.desktop){
     		document.getElementById("turn").style.display="block";
     	}
	}
	
	function handleCorrect(){
		if(!game.device.desktop){
			if(firstRunLandscape){
				gameRatio = window.innerWidth/window.innerHeight;		
				game.width = Math.ceil(640*gameRatio);
				game.height = 640;
				game.renderer.resize(game.width,game.height);
				game.state.start("Play");		
			}
			document.getElementById("turn").style.display="none";
		}
	}
	
	game.state.add("Play",play);
	game.state.start("Play");
}

We have to know if the game was launched in landscape mode, so I have a firstRunLandscape variable (line 4) which gets a Boolean value which is true if the game was launched in landscape mode (line 10), then when the device is rotated in its correct position, if we know we are coming from a landscape start (line 34), we recalculate game ratio, width and height then resize the renderer and start the state again (lines 35-41).

Now everything will work fine.

You can test it by yourself at http://www.emanueleferonato.com/wp-content/uploads/2015/04/resp/ if you have a mobile device, or download the source code.

Get the most popular Phaser 3 book

Through 202 pages, 32 source code examples and an Android Studio project you will learn how to build cross platform HTML5 games and create a complete game along the way.

Get the book

215 GAME PROTOTYPES EXPLAINED WITH SOURCE CODE
// 1+2=3
// 100 rounds
// 10000000
// 2 Cars
// 2048
// A Blocky Christmas
// A Jumping Block
// A Life of Logic
// Angry Birds
// Angry Birds Space
// Artillery
// Astro-PANIC!
// Avoider
// Back to Square One
// Ball Game
// Ball vs Ball
// Ball: Revamped
// Balloon Invasion
// BallPusher
// Ballz
// Bar Balance
// Bejeweled
// Biggification
// Block it
// Blockage
// Bloons
// Boids
// Bombuzal
// Boom Dots
// Bouncing Ball
// Bouncing Ball 2
// Bouncy Light
// BoxHead
// Breakout
// Bricks
// Bubble Chaos
// Bubbles 2
// Card Game
// Castle Ramble
// Chronotron
// Circle Chain
// Circle Path
// Circle Race
// Circular endless runner
// Cirplosion
// CLOCKS - The Game
// Color Hit
// Color Jump
// ColorFill
// Columns
// Concentration
// Crossy Road
// Crush the Castle
// Cube Jump
// CubesOut
// Dash N Blast
// Dashy Panda
// Deflection
// Diamond Digger Saga
// Don't touch the spikes
// Dots
// Down The Mountain
// Drag and Match
// Draw Game
// Drop Wizard
// DROP'd
// Dudeski
// Dungeon Raid
// Educational Game
// Elasticity
// Endless Runner
// Erase Box
// Eskiv
// Farm Heroes Saga
// Filler
// Flappy Bird
// Fling
// Flipping Legend
// Floaty Light
// Fuse Ballz
// GearTaker
// Gem Sweeper
// Globe
// Goat Rider
// Gold Miner
// Grindstone
// GuessNext
// Helicopter
// Hero Emblems
// Hero Slide
// Hexagonal Tiles
// HookPod
// Hop Hop Hop Underwater
// Horizontal Endless Runner
// Hundreds
// Hungry Hero
// Hurry it's Christmas
// InkTd
// Iromeku
// Jet Set Willy
// Jigsaw Game
// Knife Hit
// Knightfall
// Legends of Runeterra
// Lep's World
// Line Rider
// Lumines
// Magick
// MagOrMin
// Mass Attack
// Math Game
// Maze
// Meeblings
// Memdot
// Metro Siberia Underground
// Mike Dangers
// Mikey Hooks
// Nano War
// Nodes
// o:anquan
// One Button Game
// One Tap RPG
// Ononmin
// Pacco
// Perfect Square!
// Perfectionism
// Phyballs
// Pixel Purge
// PixelField
// Planet Revenge
// Plants Vs Zombies
// Platform
// Platform game
// Plus+Plus
// Pocket Snap
// Poker
// Pool
// Pop the Lock
// Pop to Save
// Poux
// Pudi
// Pumpkin Story
// Puppet Bird
// Pyramids of Ra
// qomp
// Quick Switch
// Racing
// Radical
// Rebuild Chile
// Renju
// Rise Above
// Risky Road
// Roguelike
// Roly Poly
// Run Around
// Rush Hour
// SameGame
// SamePhysics
// Save the Totem
// Security
// Serious Scramblers
// Shrink it
// Sling
// Slingy
// Snowflakes
// Sokoban
// Space Checkers
// Space is Key
// Spellfall
// Spinny Gun
// Splitter
// Spring Ninja
// Sproing
// Stabilize!
// Stack
// Stairs
// Stick Hero
// String Avoider
// Stringy
// Sudoku
// Super Mario Bros
// Surfingers
// Survival Horror
// Talesworth Adventure
// Tetris
// The Impossible Line
// The Moops - Combos of Joy
// The Next Arrow
// Threes
// Tic Tac Toe
// Timberman
// Tiny Wings
// Tipsy Tower
// Toony
// Totem Destroyer
// Tower Defense
// Trick Shot
// Tunnelball
// Turn
// Turnellio
// TwinSpin
// vvvvvv
// Warp Shift
// Way of an Idea
// Whack a Creep
// Wheel of Fortune
// Where's my Water
// Wish Upon a Star
// Word Game
// Wordle
// Worms
// Yanga
// Yeah Bunny
// Zhed
// zNumbers