Home > Articles > Home & Office Computing > eBay/Yahoo/Google

Developing HTML5 Applications, Part 2

  • Print
  • + Share This
HTML5 is a hot topic these days, and you’ll probably want to create your own HTML5 applications. If these applications are to be useful, they should work in a wide variety of browsers. Author and web developer Jeff Friesen launches a three-part series that focuses on developing portable HTML5 applications. Specifically, the article focuses on designing an entertainment-oriented application that detects the current browser and adapts to its HTML5 limitations. It also shows you how to play audio without the HTML5 audio element (in case you encounter a browser that supports the HTML5 canvas element but not audio), and shows you how to adapt the application’s canvas to cover the browser's client area for a richer experience.

Editor's Note: Be sure to read Part 1 of this 3-part series.

Like this article? We recommend

HTML5 is an exciting update to the HTML specification, and many developers are creating HTML5 applications. Because browser implementations of this specification are slowly maturing (it will be years before the HTML5 specification is finalized), some browsers do not implement certain HTML5 features correctly (or even implement these features). This lack of consistency in implementing features makes it difficult to create portable HTML5 applications.

I've created this three-part Developing HTML5 Applications series to address this problem. Part 1 provided an overview of HTML5, emphasizing features used by the applications presented in this article as well as Part 3 of this series. I also looked at testing these features in various browsers to determine browser support, and presented a JavaScript library whose browser-identification functions help developers write portable HTML5 applications.

Now that Part 1 is out of the way, we can focus on designing applications that detect the current browser and adapt to its HTML5 limitations. This article explores this topic in the context of Animated Starry Diorama (ASD, for short), an entertainment-oriented application whose only HTML5 dependencies are its audio and canvas elements. After introducing ASD and focusing on its design, the article presents an audio element alternative for browsers that don't support this feature, and shows you how to adapt the canvas to cover a browser's client area.

Introducing Animated Starry Diorama

Animated Starry Diorama (ASD) is an HTML5 application that presents an animated three-dimensional (3D) field of stars overlaying a galaxy image. This animation is accompanied by music from British composer Gustav Holst's The Planets suite.

You can try out ASD by pointing your browser to my website. Alternatively, you can access this application from this article's accompanying code file. Figure 1 shows you what the application looks like.

Figure 1 ASD is horizontally centered in Firefox's client area. Click to enlarge.

Application Design

ASD is composed of ASD.html and ASD.js, which collectively define this application. I organized ASD around a pair of files to simplify maintenance.

ASD.html provides the necessary HTML structure and a script for integrating this application into a browser. Listing 1 presents the contents of this HTML file.

Listing 1—ASD.html

<html>
  <head>
    <title>
      Animated Starry Diorama
    </title>
  </head>
  <body>
    <script type="text/javascript" src="BrowserDetect.js">
    </script>
    <script type="text/javascript">
      var version;
       if (((version = getFirefoxVersion()) != null &&
           cmp(version, "3.6") >= 0) ||
          ((version = getChromeVersion()) != null &&
           cmp(version, "6.0.472.63") >= 0) ||
          ((version = getSafariVersion()) != null &&
           cmp(version, "5.0.2") >= 0) ||
          ((version = getOperaVersion()) != null &&
           cmp(version, "10.62") >= 0))
      {
         var WIDTH = 720;
         var HEIGHT = 480;
         document.write("<div style='text-align: center'>");
         document.write("<canvas id='diorama' width='"+WIDTH+"' "+
                        "height='"+HEIGHT+"'><\/canvas>");
         document.write("<\/div>");
         document.write("<script type='text\/javascript' "+
                        "src='ASD.js'><\/script>");
      }
      else
      {
         document.write("<div style='text-align: center'>");
         document.write("Your browser cannot properly execute this "+
                        "HTML5 application.");
         document.write("<\/div>");
      }
    </script>
  </body>
</html>

ASD.html includes the BrowserDetect.js JavaScript library that I presented in Part 1 of this series. This HTML file's small script subsequently calls most of this library's functions to verify that the current browser is capable of supporting the application's HTML5 features.

If the current browser is acceptable, the script writes the necessary HTML for specifying the canvas element and including ASD.js's source code. If it's not acceptable, the script writes other HTML to inform the user that the browser cannot host the application.

The heart of the application resides in ASD.js. This file's JavaScript code consists of several global variables and functions, and inline code that initializes the application and starts it running. Listing 2 presents the contents of this JavaScript file.

Listing 2—ASD.js

// ASD.js
var canvas = document.getElementById("diorama");
var canvasctx = canvas.getContext('2d');
var xsize, ysize, xcenter, ycenter;
var buffer;
var bufferctx;
var NBGSTARS = 200;
var bgstarX = new Array();
var bgstarY = new Array();
var NSTARS = 200;
var starX = new Array();
var starY = new Array();
var starZ = new Array();
var GRAYSCALE = 0;
var RED = 1;
var BLUE = 2;
var color = new Array();
var shade = new Array();
var loaded = false;
var image = new Image();
image.src = "galaxy.jpg";
image.onload = function()
{
   loaded = true;
}
var audio;
// Initialize star data, background canvas, and audio so that an audio file is
// playing.
function init()
{
   xsize = WIDTH;
   ysize = HEIGHT;
   xcenter = xsize>>1;
   ycenter = ysize>>1;
   buffer = document.createElement('canvas');
   buffer.width = WIDTH;
   buffer.height = HEIGHT;
   bufferctx = buffer.getContext('2d');
           
   for (n = 0; n < NSTARS; n++)
   {
      starX[n] = -xsize+(random(xsize)<<1);
      starY[n] = -ysize+(random(ysize)<<1);
      starZ[n] = 1+random(4095);
      var threshold = random(100);
      if (threshold < 93)
         color[n] = GRAYSCALE;
      else
      if (threshold < 96) // fewer red stars than grayscale stars
         color[n] = RED;
      else
         color[n] = BLUE; // fewer blue stars than red stars
   }
   for (n = 0; n < 256; n++)
      shade[255-n] = n;
   for (n = 0; n < NBGSTARS; n++)
   {
      bgstarX[n] = random(xsize);
      bgstarY[n] = random(ysize);
   }
   var filename;
   if (getFirefoxVersion() != null || getOperaVersion() != null)
      filename = "neptune.ogg";
   else
   {
      if (random(2) == 0)
         filename = "neptune.mp3";
      else
         filename = "saturn.mp3";
   }
   audio = new Audio();
   audio.src = "http://tutortutor.ca/software/ASD/"+filename;
   audio.play();
}
// Return a randomly generated integer ranging from 0 through limit-1.
function random(limit)
{
   return Math.floor(limit*Math.random());
}
// Render the next frame of animation.
function renderFrame()
{
   bufferctx.fillStyle = "RGB(0, 0, 0)";
   bufferctx.fillRect(0, 0, WIDTH, HEIGHT);
   bufferctx.fillStyle = "RGB(64, 64, 64)";
   for (n = 0; n < NBGSTARS; n++)
   {
      bufferctx.beginPath();
      bufferctx.arc(bgstarX[n], bgstarY[n], 1, 0, Math.PI*2, true);
      bufferctx.fill();
   }
   if (loaded)
      bufferctx.drawImage(image, (WIDTH-image.width)>>1,
                          (HEIGHT-image.height)>>1);
   for (n = 0; n < NSTARS; n++)
   {
      starZ[n] -= 30;
      if (starZ[n] < 1)
      {
         starX[n] = -xsize+(random(xsize)<<1);
         starY[n] = -ysize+(random(ysize)<<1);
         starZ[n] = 4095;
      }
      var z = starZ[n];
      var x = (starX[n]<<9)/z+xcenter;
      var y = (starY[n]<<9)/z+ycenter;
      var radius = (4095-z)/1364; // radius ranges from 0 through 3.
      bufferctx.beginPath();
      bufferctx.arc(x, y, radius, 0, Math.PI*2, true);
      var fs = "RGB(";
      switch (color[n])
      {
         case GRAYSCALE: fs += shade[z>>4]+","+shade[z>>4]+","+shade[z>>4]+")";
                         break;
         case RED      : fs += shade[z>>4]+",0,0)";
                         break;
         case BLUE     : fs += "0,0,"+shade[z>>4]+")";
      }
      bufferctx.fillStyle = fs;
      bufferctx.fill();
   }
   canvasctx.drawImage(buffer, 0, 0);
}
// Initialize the application and specify that renderFrame() is to be called
// every 5 milliseconds.
init();
setInterval(renderFrame, 5);

ASD.js first declares several global variables. Although it's typically not a good idea to declare multiple global variables, which can lead to name conflicts when multiple script files are included, this isn't a problem for ASD because this is the only file that declares global variables.

The following global variables are declared:

  • canvas stores a reference to the canvas element created in ASD.html. This variable is present only for convenience. I could have avoided the canvas intermediary and assigned the canvas's 2D context to canvasctx via var canvasctx = document.getElementById("diorama").getContext('2d');.
  • canvasctx stores a reference to the canvas's 2D drawing context so that graphics can be rendered on the canvas.
  • xsize stores the width of the canvas. It's useful for determining the horizontal region in which stars are initially positioned.
  • ysize stores the height of the canvas. It's useful for determining the vertical region in which stars are initially positioned.
  • xcenter stores the horizontal coordinate of the canvas's center point. It's useful when converting from 3D coordinates to 2D coordinates (as explained later in this article).
  • ycenter stores the vertical coordinate of the canvas's center point. It's useful when converting from 3D coordinates to 2D coordinates (as explained later in this article).
  • buffer stores a reference to a background canvas element for use in rendering a scene to avoid star flicker.
  • bufferctx stores a reference to the background canvas's 2D drawing context.
  • NBGSTARS stores the number of background stars to draw. These stars are rendered in a dark shade of gray to appear farther away. I've capitalized this global variable name to make it appear like a constant.
  • bgstarX stores an array of horizontal coordinates for the background stars.
  • bgstarY stores an array of vertical coordinates for the background stars.
  • NSTARS stores the number of foreground stars to draw and animate.
  • starX stores an array of horizontal coordinates for the foreground stars.
  • starY stores an array of vertical coordinates for the foreground stars.
  • starZ stores an array of Z coordinates for the foreground stars.
  • GRAYSCALE stores a code that indicates that a star is to be animated in various shades of gray from black to white.
  • RED stores a code that indicates that a star is to be animated in various shades of red from black to bright red.
  • BLUE stores a code that indicates that a star is to be animated in various shades of blue from black to bright blue.
  • color stores an array of color codes for the foreground stars.
  • shade stores an array of shade codes that are shared by all foreground stars. These codes help determine the current color of each foreground star.
  • loaded stores a Boolean true value when the background galaxy image is loaded; this variable is initially set to false.
  • image stores a reference to a JavaScript Image object that is used to load the background galaxy image. Image loading takes place when the image's filename ("galaxy.jpg") is assigned to Image's src property. When the image finishes loading, the function assigned to Image's onload property is called and assigns true to loaded.
  • audio stores a reference to an HTML5 Audio object that is used to play music to accompany the animation.

ASD.js next declares several functions, starting with init(). This function contains the necessary code for initializing the canvas, selecting suitable music to play, and starting the music. The music selection takes into account that Firefox and Opera play only OGG files.

init() creates the background canvas element by calling document.createElement('canvas'). It subsequently assigns, to the background canvas's width and height properties, the same width and height as the canvas element.

The random(limit) function is a convenience. This function is called from both init() and renderFrame(), and returns an integer that ranges from 0 through one less than limit.

renderFrame() renders the next frame of animation, using a background canvas to avoid flicker. After clearing the background canvas to black, it renders the background stars, followed by the galaxy image, followed by the foreground stars onto the background canvas.

The image is rendered by calling the background context's drawImage(Image, int, int) method. Each star is rendered by calling a combination of the context's beginPath(), arc(x, y, radius, startAngle, endAngle, anticlockwise), and fill() methods:

  • beginPath() starts a path for rendering a shape—all canvas shapes apart from rectangles are built from paths.
  • arc(x, y, radius, startAngle, endAngle, anticlockwise) describes an arc that's anchored at (x, y), has a radius of radius pixels, and is drawn from startAngle to endAngle in a clockwise direction when anticlockwise is false or an anticlockwise direction when anticlockwise is true.
  • fill() renders a filled shape (call stroke() when an outlined shape is desired).

Once it finishes rendering the background canvas, renderFrame() must copy this canvas's content onto the visible canvas. This function accomplishes this task by executing canvasctx.drawImage(buffer, 0, 0);.

After the functions have been declared, ASD.js executes init() to initialize the application and setInterval(renderFrame, 5) to repeatedly execute renderFrame() every five milliseconds.

The 3D Challenge

ASD is an example of a diorama (a 3D representation of a scene, either full-size or miniature) because it generates a 3D field of stars. Although implementing 3D can be complicated, ASD's 3D implementation is not that hard to understand.

Before examining ASD's 3D-oriented code, let's review some 3D basics. First, a point in 3D space is represented by x, y, and z coordinates. It's common to locate these points according to the following 3D coordinate system:

  • An X axis that corresponds to the screen's horizontal axis: The left half of the screen's horizontal axis represents negative values and the right half represents positive values.
  • A Y axis that corresponds to the screen's vertical axis: The top half of the screen's vertical axis represents positive values and the bottom half represents negative values—in ASD, the bottom half represents positive values to avoid an extra calculation (stars look the same when plotted upside down).
  • A Z axis that is perpendicular to the screen and passes through its center point: The Z axis starts at 0 (the Z location of the viewer) and builds into the screen through increasing positive values.

ASD depends upon a technique called perspective projection to display a 3D point on a 2D screen. The idea is to move a 3D point closer to the vanishing point (an imaginary point at the center of the screen where nothing is visible) as the z value becomes move positive.

We achieve perspective by dividing a 3D point's x and y values by its z value and plotting the pixel at the resulting (x, y) location. The x and y values are multiplied by a constant before dividing by z to avoid an exaggerated perspective effect caused by small differences in z values.

Following the division, the screen's center point location is added to the resulting x and y values to derive the actual location for plotting the point. The following expressions show you how to perform this perspective projection in JavaScript:

var xScreen = x*vpd/z+xScreenCenter; // vpd is a constant representing the
var yScreen = y*vpd/z+yScreenCenter; // distance to the view plane

vpd represents the distance from 3D center point (0, 0, 0)—the location of the viewer—to the view plane (the plane on which 3D objects, such as ASD's stars, are projected). The view plane represents the screen in 3D space.

Points in front of the view plane (z is less than vpd) are not visible to the user and should be clipped so that they are not seen—displaying these points could lead to strange effects. This consideration results in the following enhancement to the previous code fragment:

if (z >= vpd)
{
   var xScreen = x*vpd/z+xScreenCenter;
   var yScreen = y*vpd/z+yScreenCenter;
}

We can enhance the 3D effect by brightening points as they move from a back-clipping plane to the view plane. Points whose z coordinates put them beyond the back-clipping plane are invisible and not displayed, and the following code fragment takes this into account:

if (z >= vpd && z <= bcpd) // bcpd is a constant (often 4095) representing the 
{                          // distance from (0, 0, 0) to the back-clipping plane
   var xScreen = x*vpd/z+xScreenCenter;
   var yScreen = y*vpd/z+yScreenCenter;
}

Now that you have sufficient information to understand ASD's 3D implementation, let's explore that implementation, beginning with the following code fragment from the init() function:

starX[n] = -xsize+(random(xsize)<<1);
starY[n] = -ysize+(random(ysize)<<1);
starZ[n] = 1+random(4095);

This code fragment assigns x, y, and z coordinate values to star n's starX, starY, and starZ array entries: x ranges from -xsize to xsize, y ranges from -ysize to ysize, and z ranges from 1 to 4095.

I chose these ranges for x and y so that, if positioned on the back-clipping plane, each star would appear within a small region of this plane centered about the middle of the screen. Of course, this assumes that z is always set to 4095.

Instead of assigning 4095 to starZ[n], I assign a randomly selected number from 1 through 4095 to this array entry. I don't include 0 in this range because it opens the possibility of division by 0, although I safeguard against this possibility later in the code.

I assign a random number to avoid a visual anomaly where all stars initially appear in a rectangular grid, move toward the view plane in unison, and disappear in unison with no additional stars appearing for a brief moment. The resulting animation looks horrible.

The second code fragment (from the renderFrame() function) is concerned with decrementing each star's value (by the same amount) so that the star moves towards the view plane, and moving the star to a new position on the back-clipping plane if its z coordinate goes out of range:

starZ[n] -= 30;
if (starZ[n] < 1)
{
   starX[n] = -xsize+(random(xsize)<<1);
   starY[n] = -ysize+(random(ysize)<<1);
   starZ[n] = 4095;
}

Ideally, I should be clipping against the view plane, so the if statement's expression should read starZ[n] < 512. However, I prefer to clip against 1 (in front of the view plane) to prevent stars that start out close to the center of the screen from disappearing prematurely.

Assuming that starZ[n] is greater than or equal to 1, I apply the perspective projection via the following code fragment (also from renderFrame()):

var z = starZ[n];
var x = (starX[n]<<9)/z+xcenter;
var y = (starY[n]<<9)/z+ycenter;

This code fragment assigns starZ[n] to z for convenience. It multiplies each of starX[n] and starY[n] by 512 to ensure that the perspective is not exaggerated—all stars would be animated in a small area around the center the screen if I did not multiply by 512.

This is pretty much it regarding ASD's 3D implementation. However, I also use the value assigned to z to calculate a star's radius (initially 0 and ranging to 3), and to determine the shade of gray, red, or blue in which to color the star.

  • + Share This
  • 🔖 Save To Your Account

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020