11 December 2011
Sound understanding of HTML and CSS. Also, basic knowledge of JavaScript and jQuery.
Additional required other products
Intermediate
HTML5 is much more than a collection of new tags, such as <header>, <article>, and <video>. It also specifies new ways to interact with web pages to make them more like applications, or apps. In this two-part tutorial, I’m going to show you how to combine the following HTML5-related technologies to create a web app based on the popular word-guessing game, Hangman:
The app also makes use of CSS3 media queries to ensure that the game displays correctly in different size devices, ranging from mobile phones to desktops. What’s more, it works in older browsers—yes, even Internet Explorer (IE) 6—thanks to JavaScript libraries such as jQuery and Modernizr.
It’s fair to warn you that building the Hangman web app involves a lot of hand coding, but I hope you’ll find it a fun way to learn about the new HTML5-related APIs (application programming interfaces). I also assume you have a good understanding of CSS and know at least the basics of JavaScript and jQuery. If you need to brush up on JavaScript and jQuery, take a look at my two "Scripting the web" articles, Introduction to JavaScript and Introduction to jQuery.
Note: You don’t need Dreamweaver CS5.5 to build the Hangman web app. All the code is standards-compliant, so any text editor will do. However, Dreamweaver’s code hinting and syntax checking helps speed up the development process and reduce errors.
Hangman is a game that seems to have been around since prehistoric times. I remember playing it as a young boy—and that’s certainly a long time ago. Traditionally, it’s played on paper. One player thinks of a word and writes down a series of underscores, one for each letter. The other player guesses a letter. If it’s in the word, the first player writes the letter above the appropriate underscores. If it isn’t, the first player starts drawing a gallows. For each bad guess, a new element is added to the gallows, followed by the victim’s head, body, arms, and legs. The idea of the game is guess the word before the victim meets his grisly fate.
The Hangman web app adapts the game for a single player. Instead of playing against a human opponent, you play against the app, which selects a word at random, and displays the correct number of underscores together with a keypad. Tap or click one of the letters. If you guess correctly, the letter is placed in the word. Otherwise, the app begins to build the gallows and its victim (Figure 1).
To make it easy to remember which letters have already been guessed, they disappear from the keypad. The gallows and its victim are built using HTML5 canvas and the Canvas 2D Context API.
If you guess the word correctly, the app uses local storage to record your score (Figure 2).
Local storage is part of the new Web Storage API, which allows you to store up to 5 MB of data on the user’s device. In addition to the large amount of data you can store, local storage has the advantage that—unlike cookies—the data doesn’t need to be sent back and forth to the web server each time the app is loaded. Even if you close the browser or work offline, it remembers your score.
Note: Because the score is stored locally, it’s unique to the device you’re playing on. It doesn’t keep track of cumulative scores if you play on different devices.
If you fail to guess the word before the game ends, the app displays the word with the missing letters highlighted in red (Figure 3).
Although Hangman is a well-known game, not everyone might know the rules, so a brief “How to Play” panel is displayed when you click or tap the question-mark icon at the top right of the screen. The panel is closed by clicking or tapping the panel’s close button (Figure 4).
Because the Hangman web app is a game that’s unlikely to change very often, if at all, it’s an ideal candidate for an HTML5 offline web application. All this involves is creating a manifest file that instructs the browser to store all the necessary files in a special application cache, separate from the standard browser cache.
Although the app utilizes many features that are supported only by the most recent browsers, you’ll be surprised to learn that it also works in IE 6 thanks to the use of Modernizr and other JavaScript libraries, which add support for all the features it uses except offline storage. The proof is in Figure 5.
There’s a lot of work ahead, so let’s plunge straight in.
If you haven’t already done so, download hangman_pt1_start.zip and extract the sample files to a Dreamweaver site. You don’t need to create a new site. The files are in a folder called hangman, which contains the files shown in Figure 6.
<head> section contains the following <meta> tag:<meta name="viewport" content="width=device-width, initial-scale=1">
This ensures that mobile devices display the app at its normal size. Without it, most smartphones scale the page down to fit a nominal viewport approximately 960 pixels wide.
<body> of the page. It looks like this:<h1>Hangman</h1>
<p id="warning">JavaScript must be enabled to play this game.</p>
<div id="help"></div>
<div id="helptext">
<h2>How to Play</h2>
<div id="close"></div>
<p>Hangman is a word-guessing game. . .</p>
</div>
<p id="loading">Game loading. . .</p>
<canvas id="stage" width="200" height="200">Sorry, your browser needs to support canvas for this game.</canvas>
<div id="play">New Game</div> <div id="clear">Clear Score</div>
<p id="word"></p>
<div id="letters"></div>
The main points to note about this markup are that there are three empty <div> elements (with the IDs help, close, and letters), an empty paragraph with the ID word, and a <canvas> element.
The help and close elements are for the icons that toggle the helptext <div> open and closed. They’re empty because the icons will be displayed only when JavaScript is enabled. The word paragraph will contain the row of underscores and letters for the word to be guessed, and the letters <div> will be populated by the alphabetic keypad. JavaScript will populate both elements dynamically.
Although IE 6–8 doesn’t support <canvas>, you’ll see how to fix that with JavaScript in Part 2 of this tutorial.
Note that the <canvas> element is invisible. Drawing on the <canvas> also relies on JavaScript. You could use CSS to add a border to the <canvas> element, but I think the finished game looks better without.
getWord(). The final line of the function selects a word at random from an array of nearly 300 words. If you want to make the game more difficult or easier, replace the words in existing array.
To simplify the script that controls the Hangman game, I used two popular JavaScript libraries, Modernizr and jQuery. Modernizr automatically detects a browser’s capabilities, allowing you to adapt your CSS and load JavaScript helper files (“polyfills”) if necessary, while jQuery smoothes out inconsistencies between different browsers’ implementations of JavaScript.
Note: To learn more about Modernizr, see Using Modernizr to detect HTML5 and CSS3 support.
Modernizr allows you to create a custom version that detects only those browser features that you’re interested in. This greatly reduces the size of file. You can’t pick and choose with jQuery, but the development version of the file is relatively small.
Note: Clicking Download in the main menu at the top of the jQuery site takes you to a different page, which offers many different options, including linking to an online version of the jQuery library. For the purposes of this tutorial, I suggest downloading the file to your local computer.
Rather than attaching all the external JavaScript files directly to index.html, the Hangman game uses Modernizr to load jQuery and hangman.js. The advantage of doing so will become apparent in Part 2 of this tutorial, when Modernizr will conditionally load extra JavaScript files depending on the browser’s capabilities.
</head> tag in index.html like this:<script src="scripts/modernizr.hangman.js"></script>
</head>
load() method. After both files have been loaded, you need to execute a function to initialize the game. Add the following <script> block just after the line of code you inserted in the previous step:<script>
Modernizr.load({
load: ['scripts/jquery-1.7.min.js', 'scripts/hangman.js']
});
</script>
This passes the load() method a JavaScript object with a single property, load, which contains an array with the relative paths to the jQuery and hangman.js files. Edit the opening <html> tag in index.html to add a class called no-js like this:
<html class="no-js">
no-js class with a series of classes that reflect the capabilities of Dreamweaver’s embedded WebKit browser. The <html> tag should now look like this:<html class=" js canvas canvastext no-localstorage">
This indicates that Dreamweaver’s Live view supports JavaScript, canvas, and canvas text, but not local storage.
The page now looks like Figure 11 in Live view because CSS descendant selectors for .js #warning and .js #helptext have the display property set to none, and the style rule for .js #help contains the following properties:
background-image: url(../images/icons.png);
background-position: 2px 2px;
background-repeat: no-repeat;
Note: If you are using a different editor or a version of Dreamweaver older than CS4, test the page in a browser and use the browser’s developer tools to inspect the generated code.
If the page is loaded into a browser with JavaScript disabled, it looks similar to Figure 7, but without the “Game loading” message, which is hidden by setting the display property to none in a style rule called .no-js #loading.
The script that controls the Hangman game needs to perform the following tasks:
There’s a lot to do, but identifying the individual tasks makes it easier to break the script into dedicated functions.
The first stage in scripting the Hangman game is to initialize a small number of variables that will be used throughout the script and to set up the controls.
getWord() function at the top, but it’s a good idea to organize your code in a logical order for easier maintenance.<canvas> element, the paragraph that displays the underscores and guessed letters, and to the <div> that displays the alphabetic keypad. You also need to keep track of the word to guess, how many letters it contains, and the number of good and bad guesses. Declare these as global variables at the top of the script like this:// Global variables
var canvas = document.getElementById('stage'),
word = document.getElementById('word'),
letters = document.getElementById('letters'),
wordToGuess,
wordLength,
badGuesses,
correctGuesses;
The first three declarations store references for the stage, word, and letters IDs. Although the jQuery library has been loaded, I have used the standard JavaScript getElementById() method rather than the jQuery shortcut $(). This is because the reference to the <canvas> element needs to be a standard JavaScript object, not a jQuery object. I could have used jQuery for word and letters, but decided that using standard objects for all global variables made the code internally consistent and easier to maintain. The other four variables aren’t assigned any values at this stage because they’ll be different with every game.
Note: It’s generally recommended to avoid using global variables—or at least use as few as possible—to avoid potential conflicts with other scripts. However, I decided it was safe to use these seven because the Hangman game is standalone. Declaring all the globals at the top of the script makes it easier to identify them in the event of a conflict.
function init() {
var helptext = $('#helptext'),
w = screen.availWidth <= 800 ? screen.availWidth : 800;
// Hide the loading message and display the control buttons
$('#loading').hide();
$('#play').css('display', 'inline-block').click(newGame);
$('#clear').css('display', 'inline-block').click(resetScore);
}
This declares two local variables that will be used to display the help panel. The first variable gets a reference to the helptext <div>. The other declaration uses the ternary (conditional) operator to check if the available width of the viewport inside the browser is 800 pixels or less. If it is, the actual width is assigned to w. If the viewport is wider, w is set to 800.
The remaining three lines use jQuery methods to hide the loading <div> and set the display property of the New Game and Clear Score buttons to inline‑block. The click() method also binds functions called newGame() and resetScore() to the appropriate buttons. Note that when binding functions, you use only the function name without the trailing parentheses.
newGame() and resetScore() functions later, but you need to add dummy definitions to prevent the script from generating errors.function newGame() {
alert('Game started');
}
function resetScore() {
alert('Score has been reset');
}
init() function. Do this by adding a second property called complete to the JavaScript object that’s passed as an argument to Modernizr.load(). The revised <script> block in the <head> of index.html looks like this:<script>
Modernizr.load({
load: ['scripts/jquery-1.7.min.js', 'scripts/hangman.js'],
complete: function() {
init();
}
});
</script>
The complete property expects an anonymous function containing any code you want to be executed after all JavaScript files have been loaded. In this case, it simply calls the init() function in hangman.css.
Note: Don’t forget to add the comma after the array containing the value of the load property.
Note: If the page doesn’t look like Figure 12 or the buttons don’t work, load the page into a browser and check the console in the browser’s developer tools for errors. JavaScript is case-sensitive, so a simple misspelling can prevent your script from working. Also make sure that the jQuery external file is attached before hangman.js.
<div> that contains the help icon. The following code goes inside the init() function, just before the closing curly brace:$('#help').click(function(e) {
$('body').append('<div id="mask"></div>');
helptext.show().css('margin-left', (w-300)/2 + 'px');
});
This binds an anonymous function that is executed when the help <div> is clicked. The first line inside the function appends an empty <div> with the ID mask to the <body>. This has the effect of masking the entire page in semi-transparent black using the following style rule in hangman.css:
#mask {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.8);
}
The second line inside the function displays the helptext <div>, and calculates its left margin to center it in the browser viewport. The calculation deducts the overall width of the help panel (260px, plus 20px each of padding and border) from w, which is the lesser of the actual width of the viewport or 800px, and divides it by 2. The style rules for .js #helptext assign it a z‑index of 10, so it sits on top of the mask.
init() function immediately after the code you inserted in the preceding step:$('#close').click(function(e) {
$('#mask').remove();
helptext.hide();
});
This is fairly straightforward. It removes the mask <div> and hides the helptext <div>. You need to remove the mask <div> completely from the page’s Document Object Model (DOM) because a new one is added each time the help icon is clicked.
When the user clicks or taps the New Game button, the script needs to select a word at random, and display a series of underscores representing the letters to be guessed, as well as the alphabetic keypad.
newGame() function, and declare the following local variables:function newGame() {
var placeholders = '',
frag = document.createDocumentFragment(),
abc = ['A','B','C','D','E','F','G','H','I','J','K','L','M',
'N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
}
The placeholders variable is used to store the series of underscores representing the word to be guessed. It’s initialized as an empty string.
The other two variables are for the alphabetic keypad, which is stored in a temporary document fragment before being added to the DOM. This avoids the need for the browser to repaint the screen after creating the key for each letter.
newGame() function:badGuesses = 0;
correctGuesses = 0;
wordToGuess = getWord();
wordLength = wordToGuess.length;
// create row of underscores the same length as letters to guess
for (var i = 0; i < wordLength; i++) {
placeholders += '_';
}
word.innerHTML = placeholders;
The final line uses the standard JavaScript innerHTML property to insert the underscores in the word paragraph. You need to use innerHTML rather than the jQuery html() method because word is one of the global variables stored as a standard JavaScript object.
abc array and adding a <div> containing the current letter to the document fragment stored in frag. Add this code to the newGame() function:// create an alphabet pad to select letters
letters.innerHTML = '';
for (i = 0; i < 26; i++) {
var div = document.createElement('div');
div.style.cursor = 'pointer';
div.innerHTML = abc[i];
div.onclick = getLetter;
frag.appendChild(div);
}
letters.appendChild(frag);
The first line of this new code sets the innerHTML property of the letters <div> to an empty string to clear any content from a previous game. Then a loop runs 26 times (once for each letter of the alphabet).
Each time the loop runs, it creates a new <div>, sets its cursor property to pointer, sets its innerHTML to the current letter, adds a function called getLetter() as its click event handler, and then appends the <div> to the document fragment.
Finally, the document fragment is appended as the content of the letters <div>.
getLetter() function. Add this to hangman.js after the newGame() definition:function getLetter() {
alert('You selected ' + this.innerHTML);
}
getLetter() function and create a dummy checkLetter() function like this:// Get selected letter and remove it from the alphabet pad
function getLetter() {
checkLetter(this.innerHTML);
this.innerHTML = ' ';
this.style.cursor = 'default';
this.onclick = null;
}
// Check whether selected letter is in the word to be guessed
function checkLetter(letter) {
}
This passes the selected letter to checkLetter(), and replaces the letter in the keypad with a nonbreaking space to prevent the <div> from collapsing when the letter is removed. The cursor property is reset to default and the click event handler is set to null, preventing the same letter from being selected twice.
The styles in hangman.css need to be adjusted to arrange the alphabetic keypad in a neat block. Because the game will be viewed on a wide variety of devices, you need to use CSS3 media queries to set the appropriate values for different screen resolutions. After a lot of experimentation, I came up with the following rules, which should be added at the bottom of the style sheet:
@media only screen and (max-width: 399px) {
h1 {
margin-top: 0.2em;
margin-bottom: 0;
font-size: 150%;
}
#word {
text-align:center;
margin-left: 0;
}
}
@media only screen and (max-width: 399px), only screen and (min-width: 530px) {
#letters {
width: 320px;
}
body {
max-width: 560px;
margin: 0 auto;
}
}
@media only screen and (min-width: 400px) and (max-width:439px) {
#letters {
width: 180px;
}
}
@media only screen and (min-width: 440px) and (max-width: 529px) {
#letters {
width: 225px;
}
}
@media only screen and (min-width: 700px) {
body {
max-width: 800px;
}
#stage {
width: 300px;
height: 300px;
}
}
Although IE6–8 don’t understand media queries, the custom Modernizr script that you added to index.html earlier includes a media query polyfill, so these style rules will also be applied by older browsers. The polyfill relies on JavaScript being enabled, but so does the Hangman game.
The final media query increases the width and height of the <canvas> element from 200px to 300px when the browser viewport is 700 pixels or more wide. To fill this larger space, you need to scale the <canvas> element’s drawing context by adding the following code to the init() function in hangman.js (insert the code just before the function’s closing curly brace):
// Rescale the canvas if the screen is wider than 700px
if (screen.innerWidth >= 700) {
canvas.getContext('2d').scale(1.5, 1.5);
}
This checks the screen’s innerWidth property. If it’s 700 pixels or more, the <canvas> element’s drawing context is scaled horizontally and vertically by a ratio of 1.5—in other words, increased by 50 percent. You’ll learn about the drawing context in Part 2 of this tutorial series.
The Hangman web app is beginning to take shape. The help panel and New Game button are now active. If you want to check your code so far, there’s a version of the app at its current stage of development in hangman_pt1_finish.zip.
In Part 2, you'll add the programming logic that runs the game, checking each guess and adding the letter in the right position(s) or drawing the gallows and victim. You'll also implement the app's local and offline storage and add support for canvas and canvas text in older versions of Internet Explorer.