devxlogo

A 3D Exploration of the HTML Canvas Element

A 3D Exploration of the HTML Canvas Element

ector graphics abound on the web, and they come in a variety of formats, including Flash and SVG. HTML Canvas, one of the newer incarnations, occupies a different niche from other vector graphics systems. While SVG is a declarative graphics file format that can be rendered by any kind of program and Flash is built around a complete multimedia system (including browser plug-in libraries, the ActionScript scripting language, and content-creation tools), HTML Canvas is HTML. In fact, HTML Canvas is part of the upcoming HTML 5 specification. As such, the HTML Canvas is integrated into the DOM tree, which means it can be accessed from JavaScript. Thus, the HTML Canvas allows you to do many of the things that Flash and SVG renderers can do.

The HTML Canvas bridges the gap between HTML markup and individual pixels. It allows you to efficiently draw arbitrary graphics at the level of individual drawing primitives or even at the level of individual pixels. And it lets you do it right from JavaScript.

This article describes the implementation of a simple 3D game using the HTML Canvas (HC). HC currently is designed for 2D graphics, but in the end, 3D graphics are rendered as 2D graphics, so HC is fine for 3D as well. And, because HC is implemented natively, you can get a pretty decent frame rate. (Click here to download the source code for the 3D game.)

To begin, the following section goes over the basics of setting up, drawing, and using an HC.

The Basics of the HTML Canvas
To understand the HC better, consider the basic model it uses. The rendering of a graphics primitive is affected by many parameters. If all of these were parameters to a function, it might look like this:

  function drawLine( x, y, width, dotted_style, end_cap_style,                     transparency, clip_rect, anti_alias,                     anti_alias_depth,                     use_graphics_hardware_if_available,                     ...                     ...                     ... );

As you can imagine, this style gets cumbersome. That’s why many graphics APIs use something called a state machine. In the state machine model, settings such as dotted_style, anti_alias, end_cap_style, and so on, are settings of the graphics context rather than parameters to a primitive function.

See also  Custom Java Web Development - The Heartbeat of Modern Web Development

This way, you can set the parameters you need, and ignore the ones you don’t. You can also use group settings for multiple primitives, as follows:

  context.fillStyle = "#f00";  context.strokeStyle = "#0f0";  context.fillRect( 20, 20, 60, 60 );  context.fillRect( 120, 120, 60, 60 );  context.strokeRect( 120, 120, 60, 60 );

Save and Restore
One of the advantages of the HC’s state machine model is also a disadvantage. After you set a parameter like fillStyle, that setting will affect all primitives drawn thereafter. This saves a lot of typing, but it can also cause a lot of headaches if the settings of one part of the code affect another part of the code.

The solution for this blessing/curse dilemma consists of a pair of methods: save() and restore(). The save() method will store the current graphics settings, and the restore() method will bring those settings back. Any changes you make to the settings in-between will disappear after restore() is called.

Here’s an example of how to use it:

  function wacky() {    context.save();    context.fillStyle = "#abc";    context.globalAlpha = .8;    context.lineWidth = 23;    context.restore();  }  function normal() {    context.strokeStyle = "#f00";    context.strokeRect( 0, 0, 50, 50 );    wacky();    context.strokeRect( 50, 50, 50, 50 );  }

The function wacky() makes a bunch of wacky changes to the graphics context, presumably to render something wacky-looking. But the changes are sandwiched between calls to save() and restore(), so when wacky() returns, all the changes are gone. That’s why wacky() can be called from normal() in between two calls to strokeRect() without your having to worry that the two rectangles will look different.

Note also that you can nest calls to save() and restore(), because they actually store graphics contexts on a stack:

  context.save();  // change state  // draw draw draw  context.save();  // change state even more  // draw draw draw  context.restore();  // change state back to the first set of changes  // draw draw draw  context.restore();

The first and third batches of drawing commands operate under the same context.

The Graphics Context
You don’t draw directly to the HC. Rather, you acquire a 2D graphics context from the canvas and draw to that:

  var context = canvas.getContext( "2d" );  context.fillStyle = "#fff";  context.strokeRect( 20, 20, 60, 60 );

The first line gets the graphics context. (Note that it specifies “2d”; a “3d” context will be in available the future.) The second line sets the current color, and the third line draws a rectangle.

See also  Custom Java Web Development - The Heartbeat of Modern Web Development

The Two Ways of Creating an HC
Like with most HTML elements, you can create an HC in two ways. The first way is to declare it using markup:

    

If you use this approach, your code will need to call getElementById() to get the canvas object:

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

The other way is to create it programmatically using JavaScript:

  var canvas = document.createElement( "canvas" );  canvas.style.border = "1px solid";  canvas.width = 400;  canvas.height = 400;  canvas.style.width = 400;  canvas.style.height = 400;  document.documentElement.appendChild( canvas );

These two methods produce the same result, so use whichever one is more convenient. The 3D game in this article utilizes the second method.

3D Rendering
Now that you know the basics of drawing with the HTML Canvas, take the 3D rendering game included in the source code download. This article covers only the highlights, because the details are beyond its scope.

The 3D world is composed of a grid. Each element of the grid can be either filled or empty. Rendering this grid means turning walls into 3D polygons, projecting those 3D polygons into 2D, and drawing the 2D polygons to the HC.

To support this, the 3D game in the downloadable source code has classes representing 2D and 3D points called P2 and P3, as well as polygon classes named Polygon2 and Polygon3. The Polygon3.project() method projects the polygon onto the 2D surface, producing a Polygon2. The Polygon2, in turn, can draw itself to the HC using moveTo and lineTo methods.

Since the HC takes care of the heavy lifting?filling polygons?the 3D code is pretty fast, even though it’s in JavaScript and not a more traditional graphics language like C.

See also  Custom Java Web Development - The Heartbeat of Modern Web Development

Transformations
Two-dimensional spatial transformations are crucial to graphics programming. They let you scale, rotate, and translate any drawing by any measure. This works especially well with vector graphics because the transformations don’t reduce rendering quality.

Transformations also follow the state machine model, so you should use them with save() and restore(). Here’s the code that renders the point-of-view arrow in the map window of the 3D system:

  function render_arrow( ctx, x, y, scale, rot ) {    // Save the current graphics state, including transformation state.    ctx.save();    // Move the arrow into place.    ctx.translate( x, y );    ctx.scale( scale, scale );    ctx.rotate( rot );    // Draw the arrow.    ctx.strokeStyle = "#aaa";    arrow.fill( ctx );    // Restore the original graphics state    ctx.restore();  }

Capturing Screen Shots
One of the coolest aspects of the HC is that you can read and write the individual pixels of the rendered image. A great use of this is to capture a screen shot. The context.toDataURL() method returns a data URL containing the image encoded in .PNG format, which looks like this:

  data:image/png;base64,iVBORw0KGgoAAAANSUh[... much more data removed ...]

You can use this URL as any other. For example, you could open a new window with it and that window would show the image. In the 3D game, there’s an extra rectangle below the view and map rectangles. Pressing Shift-S will take a screenshot and display it in this rectangle. From there, you can save the image, view it in its own window, or drag it to your desktop (if your operating system supports that).

HTML Canvas Bridges HTML and Pixels
Over the years, web designers and programmers have put a great deal of work into tricking HTML elements and CSS style declarations into doing unusual things?all in the name of pixel-accurate layout. It’s always been hard to attain pixel-level accuracy, because HTML was meant to protect you from layout details. As you have learned from the examples in this article, the HTML Canvas enables you to bridge the gap between HTML markup and individual pixels.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist