Skip to content

Commit

Permalink
Doc updates.
Browse files Browse the repository at this point in the history
Revamped adjustViewport function to work cross devices.
  • Loading branch information
martindrapeau committed Mar 21, 2015
1 parent ae8f853 commit 1ae93fd
Show file tree
Hide file tree
Showing 8 changed files with 85 additions and 60 deletions.
3 changes: 1 addition & 2 deletions ball/main.js
Expand Up @@ -102,7 +102,6 @@ $(window).on("load", function() {
engine: engine
});

// Ensure the canvas is always visible and centered
adjustViewport(canvas, 960, 700);
adjustViewport(canvas);

});
1 change: 1 addition & 0 deletions frog/index.html
Expand Up @@ -8,6 +8,7 @@
<link href="../apple_touch_icon.png" rel="apple-touch-icon" />

<meta name="viewport" content="width=960, user-scalable=no"/>
<meta name="mobileoptimized" content="0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
Expand Down
4 changes: 1 addition & 3 deletions frog/main.js
Expand Up @@ -11,6 +11,7 @@ $(window).on("load", function() {

var canvas = document.getElementById("foreground"),
context = canvas.getContext("2d");
adjustViewport(canvas);

var spriteNames = [
"land1", "land2", "land3", "land4", "land5", "land6",
Expand Down Expand Up @@ -232,8 +233,5 @@ $(window).on("load", function() {
context: context,
controller: controller
});

// Ensure the canvas is always visible and centered
adjustViewport(canvas, 960, 700);

});
79 changes: 41 additions & 38 deletions index.html
Expand Up @@ -2,7 +2,7 @@
<html lang="en">
<head>
<title>Backbone Game Engine</title>
<meta name="description" content="Elementary HTML5 Canvas Game Engine based on Backbone.">
<meta name="description" content="Elementary HTML5 Canvas Game Engine based on Backbone. Specialized for 2D platformers, and optimized for mobile.">
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link href="favicon.ico" rel="shortcut icon" type="image/x-icon" />
Expand Down Expand Up @@ -50,7 +50,7 @@ <h1>HTML5 Canvas &amp; Backbone</h1>
<div class="row">
<div class="col-md-6 col-xs-6">
<p>
An elementary HTML5 Canvas game engine built on Backbone. Examples:
An elementary HTML5 Canvas game engine built on Backbone. Specialized for 2D platformers, and optimized for mobile. Examples:
</p>
<ul>
<li>Elementary: <a href="ball/index.html" target="_blank">Bouncing ball</a></li>
Expand Down Expand Up @@ -84,14 +84,15 @@ <h3>Features:</h3>
<ul>
<li><strong>Built on Backbone</strong>. Events, models, collections, inheritance and RESTful persistence. Why reinvent the wheel?</li>
<li><strong>HTML5 canvas only</strong>. No jQuery, as little DOM manipulations as possible.</li>
<li><strong>Mobile optimized</strong>. Built to run in the browser or in CocoonJS/Phone Gap. Optimized for maxium FPS.</li>
<li><strong>2D platformer</strong>. Built with side-scrollers in mind. Built-in classes for sprites, sprite sheets, characters, hero, quad-tree collision detection, world and editor.</li>
<li><strong>No compilation</strong>. You don't need to install node, grunt or whatever else. Just code and press F5 to run.</li>
<li><strong>No server required</strong>. Fork this repo and your Github site is up and going. Create your own game and point your friends to it. Rebase to pull in latest engine updates.</li>
<li><strong>Built for mobile</strong>. Conceived to run on tablets. Share your URL with Mom so she can add it to the home screen of her iPad.</li>
<li><strong>Take if offline</strong>. With HTML5 Application Cache, your game runs offline. Perfect for taking it on the road or on a fishing trip.</li>
<li><strong>Save state</strong>. With HTML5 Local Storage, save where you are.</li>
<li><strong>World editor</strong>. Conceived for tile-based games, comes with a world editor. Place your tiles and characters, then hit play to try it out. Hit save to save your world.</li>
</ul>

</div>
</div>

Expand Down Expand Up @@ -1545,50 +1546,52 @@ <h1>Mobile Devices</h1>
</p>
<h3>Touch Events</h3>
<p>
<a href="#documentation-Input">Backbone.Input</a>, <a href="#documentation-Button">Backbone.Button</a> and <a href="#documentation-WorldEditor">Backbone.WorldEditor</a> support touch and mouse events transparently. Works on Android, iOS and Windows.
<a href="#documentation-Engine">Backbone.Engine</a>, <a href="#documentation-Input">Backbone.Input</a>, <a href="#documentation-Button">Backbone.Button</a> and <a href="#documentation-WorldEditor">Backbone.WorldEditor</a> support touch and mouse events transparently. Works on Android, iOS and Windows.
</p>
<h3>Viewport resizing and canvas centering</h3>
<p>
In addition, we provide global function <code>adjustViewport(canvas, width, height)</code> to dynamically adjust the viewport size and center the canvas when an orientation change occurs. This function can be found in <a href="https://github.com/martindrapeau/backbone-game-engine/blob/gh-pages/src/adjust-viewport.js" target="_blank">adjust-viewport.js</a> located in the <coce>src</coce> folder.
</p>
<p>
On mobile devices the orientation of the web page can change between protrait and landscape. Since we <a href="http://stackoverflow.com/questions/5298467/prevent-orientation-change-in-ios-safari" target="_blank">cannot lock the orientation</a>, we must find a way to adjust the viewport width to ensure the canvas is always visible. We use the meta tag <a href="https://developer.apple.com/library/IOs/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html" target="_blank">viewport</a> for that purpose.
</p>
<p>
As an example, let's assume our canvas is 960x700 pixels; therefore in landscape. In order for it to be visible and maximized on any portrait screen, we must control the width of the viewport. In <code>index.html</code> we declare the viewport meta tag to be the width of the canvas:
On mobile devices, the <code>meta</code> tag <code>viewport</code> is set to 960 pixels wide.
On iOS, Android and Windows mobile devices, this will ensure the canvas is full width.
The HTML file contains the necessary header tags to ensure everything works.
You can change the viewport width value to whatever you want.
</p>
<pre>&lt;meta name="viewport" content="width=960, user-scalable=no"/&gt;</pre>
<pre>
&lt;meta name="viewport" content="width=960, user-scalable=no"/&gt;
&lt;meta name="mobileoptimized" content="0" /&gt;
</pre>
<p>
When the device is rotated to landscape, we need to adjust so that the height of the canvas is used instead.
The natural solution would be to set <code>content="height=700, user-scalable=no"</code>.
However this is not supported (<a href="http://www.quirksmode.org/blog/archives/2013/10/viewport_resear.html#link2" target="_blank">as reported by Quirksmode</a>).
Instead, we must derive the width based on the viewport ratio using this formula:
Not all screens have the same aspec ratio.
To take care of the height, you can change the height of the canvas upon start by calling the global function <code>adjustViewport()</code> (see file <code>adjust-viewport.js</code> for details).
</p>
<pre>700 * window.innerWidth / window.innerHeight</pre>
<pre>
var canvas = document.getElementById("foreground");
adjustViewport(canvas);
</pre>
<p>
Since the mobile device orientation changes when we rotate it, we must recalculate and reset the viewport width dynamically. We can do so using the <code>resize</code> event. Finally, we are also able to center the canvas by adjusting its <code>left</code> position. Here is the final code:
If you want to maintain the aspect ratio, pass true. The canvas will be centered on screen.
</p>
<pre>
function adjustViewport(canvas, width, height) {

var viewport = document.querySelector("meta[name=viewport]");

function onResize() {
if (window.innerWidth > window.innerHeight) {
// Landscape
canvas.style.left = _.max([0, (window.innerWidth - width) / 2]) + "px";
viewport.setAttribute("content", "width=" + Math.ceil(height * window.innerWidth / window.innerHeight) + ",user-scalable=no");
} else {
// Portrait
canvas.style.left = "0px";
viewport.setAttribute("content", "width=" + width + ",user-scalable=no");
}
}

window.addEventListener("resize", _.debounce(onResize, 300));
onResize();
}
<pre>
var canvas = document.getElementById("foreground");
adjustViewport(canvas, true);
</pre>
<p>
On desktop the <code>viewport</code> meta tag is ignored.
<code>adjustViewport</code> will center the canvas, even handling resizes.
It will try to reduce the height of the canvas if too tall unless you omit the <code>keepRatio</code> argument.
</p>
<h3>Web App</h3>
<p>
These meta tags are set to enable Web App support:
</p>
<pre>
&lt;meta name="apple-mobile-web-app-capable" content="yes" /&gt;
&lt;meta name="mobile-web-app-capable" content="yes" /&gt;
&lt;meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/&gt;
</pre>
<p>
To suggest users to put add the home page to the home screen, checkout this great plugin:
<a href="https://github.com/cubiq/add-to-homescreen" target="_blank">Cubiq's Add To Homescreen</a>.
</p>
</div>
</div>

Expand Down
1 change: 1 addition & 0 deletions mario/index.html
Expand Up @@ -8,6 +8,7 @@
<link href="../apple_touch_icon.png" rel="apple-touch-icon" />

<meta name="viewport" content="width=960, user-scalable=no"/>
<meta name="mobileoptimized" content="0" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/>
Expand Down
6 changes: 3 additions & 3 deletions mario/main.js
Expand Up @@ -19,6 +19,7 @@ $(window).on("load", function() {
});

var canvas = document.getElementById("foreground");
adjustViewport(canvas);

var spriteSheets = new Backbone.SpriteSheetCollection([{
id: "mario",
Expand All @@ -38,6 +39,8 @@ $(window).on("load", function() {

var mario = new Backbone.Mario({
x: 400, y: 200, floor: 500
}, {
input: input
});

var world = new Backbone.World({
Expand Down Expand Up @@ -68,8 +71,5 @@ $(window).on("load", function() {
world: world,
mario: mario
});

// Ensure the canvas is always visible and centered
adjustViewport(canvas, 960, 700);

});
47 changes: 36 additions & 11 deletions src/adjust-viewport.js
Expand Up @@ -9,31 +9,56 @@
*
*/

// Ensures the canvas is always visible and centered by adjusting
// the viewport and the canvas left position.
var resizeCount = 0;
function adjustViewport(canvas, width, height) {
// Ensures the canvas is always full size and/or centered.
//
// On mobile:
// Works in conjunction with meta tag viewport where width is set to the canvas width.
// <meta name="viewport" content="width=960, user-scalable=no"/>
// Will modify the canvas height to fit the device aspect ratio, never exceeding the
// origin canvas height.
// Set keepRatio to true to maintain your aspect ratio. In such a case, the viewport
// is adjusted to the height of the canvas. The canvas is kept centered on screen (with
// black bars left and right to fill empty space).
//
// On Desktop:
// The viewport meta tag is ignored. Instead, the canvas is kept centered. The height
// may be reduced if the window height is less than the canvas (to avoid scrolling).
// Set keepRatio to maintain the canvas height.

var viewport = document.querySelector("meta[name=viewport]");
function adjustViewport(canvas, keepRatio) {

var viewport = document.querySelector("meta[name=viewport]"),
mobile = "onorientationchange" in window ||
window.navigator.msMaxTouchPoints ||
window.navigator.isCocoonJS;

function onResize() {
if (window.innerWidth > window.innerHeight) {
// Landscape
canvas.style.left = _.max([0, (window.innerWidth - width) / 2]) + "px";
viewport.setAttribute("content", "width=" + Math.ceil(height * window.innerWidth / window.innerHeight) + ",user-scalable=no");
canvas.style.left = _.max([0, (window.innerWidth - canvas.width) / 2]) + "px";
if (mobile && viewport)
viewport.setAttribute("content", "width=" + Math.ceil(canvas.height * window.innerWidth / window.innerHeight) + ",user-scalable=no");
} else {
// Portrait
canvas.style.left = "0px";
viewport.setAttribute("content", "width=" + width + ",user-scalable=no");
if (mobile && viewport)
viewport.setAttribute("content", "width=" + canvas.width + ",user-scalable=no");
}
}

window.addEventListener("resize", _.debounce(onResize, 300));
setTimeout(onResize, 10);
if (mobile && !keepRatio) {
canvas.height = Math.round(Math.min(canvas.height, canvas.width * Math.min(window.innerHeight, window.innerWidth) / Math.max(window.innerHeight, window.innerWidth) ));
} else {
if (!keepRatio)
canvas.height = Math.round(Math.min(canvas.height, window.innerHeight));
window.addEventListener("resize", _.debounce(onResize, 300));
setTimeout(onResize, 10);
}

}

_.extend(window, {
adjustViewport: adjustViewport,
adjustViewport: adjustViewport
});

}).call(this);
4 changes: 1 addition & 3 deletions super-mario-bros/main.js
Expand Up @@ -12,6 +12,7 @@ $(window).on("load", function() {

var canvas = document.getElementById("foreground"),
context = canvas.getContext("2d");
adjustViewport(canvas);

var spriteNames = [
"ground", "brick-top", "brick", "ground2", "block", "block2", "question-block", "pennie",
Expand Down Expand Up @@ -250,7 +251,4 @@ $(window).on("load", function() {
controller: controller,
});

// Ensure the canvas is always visible and centered
adjustViewport(canvas, canvas.width, canvas.height);

});

0 comments on commit 1ae93fd

Please sign in to comment.