Complement canvas with HTML markup, Part 2: Animation and text rendering

Overcome obstacles with layering

HTML canvas excels in many ways, including the great performance that comes with low overhead and direct pixel manipulation. However, canvas falls short in a few areas where HTML does exceedingly well: text rendering, SEO, accessibility, and device-independent markup. Part 1 of this series compared and contrasted the strengths of the traditional HTML model and the canvas API. It also explored hybrid HTML/Canvas applications. In this conclusion to the series, learn how to implement a sample application that involves a canvas implementation of text rendering, and how to create a canvas-based game with a rich HTML-based user interface that combines the strengths of both approaches.

Share:

Ryan DeLuca (ryan.deluca@nerdery.com), Software developer, The Nerdery

Ryan DeLuca photoRyan DeLuca began programming in 1998 and, after a few years, turned his hobby into a paying freelance career. He decided to formalize his training and graduated in 2011 from the University of Wisconsin-Superior with a bachelor of science degree. Shortly after graduation, Ryan joined The Nerdery as a software engineer specializing in PHP, JavaScript, CSS, and HTML. His many talents also include relational databases/SQL and graphics manipulation.



Kevin Moot (kmoot@nerdery.com), Software Developer, The Nerdery

Kevin Moot photoKevin Moot has had an interest in computer graphics since creating games as a wee lad on his Apple IIe (with its vast array of six colors and a mind boggling 280x192 resolution). He has worked with HTML5's Canvas technology for several cutting-edge websites, and counts HTML/CSS, JavaScript, and .NET among his specialties. Kevin currently is an interactive software developer at The Nerdery.



28 August 2012

Also available in Chinese Russian Japanese Portuguese

Introduction

Frequently used abbreviations

  • CSS: Cascading Style Sheets
  • DOM: Document Object Model
  • HTML: HyperText Markup Language
  • UI: User Interface

Part 1 of this two-part series discussed combining the best aspects of canvas and HTML elements to build rich internet applications.

In this article, review the criteria for selecting a canvas or HTML-centric architecture, and learn about animation considerations and overcoming text rendering limitations by building the foundation for a video game that layers HTML and canvas elements to harness the advantages of each approach. Figure 1 shows the groundwork of the example space shooter game in this article.

Figure 1. Sample application combining HTML and canvas elements
Sample application combining HTML and canvas elements

You can download the source code for the examples used in this article.


Architecture

When architecting an application with significant graphical components, interactive experiences, and visualizations, it's important to be aware of all of the tools available for the job. This section explores using HTML for implementing UI components and canvas for implementing animated components.

User interfaces

Though canvas has the potential for impressive graphical performance, it might not always be the best choice for a rich UI; HTML elements may be better suited. Many rich Internet applications consist of various pieces that each have different purposes and requirements. A hybrid approach that combines the best aspects of canvas and HTML elements could work best to accomplish your goals.

The sample application employs the layering technique in Figure 2. The canvas surface will predominantly be responsible for realtime graphics and animations, while several HTML elements will be overlaid on top to make up various UI components.

Figure 2. Layering HTML elements above a canvas
Layering HTML elements above a canvas

Consider the requirements of the individual pieces of an application when deciding on the best approach. As a general rule, components of the UI that require a high level of user interactivity—but do not require realtime updates—are typically best suited for the HTML layer. These elements might contain text, hyperlinks, and form elements.

For example, although the sample application may be predominantly a canvas, it has a chat window component that uses purely HTML markup. This is illustrated in Figure 3,

With simple HTML tags and CSS rules you can easily create interactive UI components such as text boxes, scrollbars, and buttons. Why spend many hours of development effort attempting to mimic the look and behavior of UI components in canvas when the browser will do it for you out-of-the-box?

Figure 3. Chat window implemented in HTML
Chat window implemented in HTML

In the chat window, there are no animations involved, and updates to the content are relatively infrequent (whenever a new chat comes in). Therefore, this is a good candidate for an HTML implementation.

Conversely, canvas provides a much better solution for content that:

  • Must be updated frequently (for example, every millisecond)
  • Requires a constant animation cycle
  • Requires only a small degree of user interactivity

Animation

Animated content is becoming more prevalent in websites. Whether you want simple animations intended to liven up a website (such as navigation transitions), or a more advanced browser-based game, several options are at your disposal. Often, animations can be accomplished relatively easily using an HTML/CSS model, and use of canvas would be overkill.

Many libraries, such as jQuery, offer convenient tools that provide consistent cross-browser output. By combining the tools from these libraries with a bit of CSS knowledge, you can greatly reduce the effort to accomplish animations—even those that are fairly complex in nature.

In the example, the ship element in Figure 4 moves and rotates according to player control. This behavior can be achieved solely by using HTML/CSS animations. No knowledge of canvas is necessary.

Figure 4. Animated ship element
Animated ship element on a black background

The HTML/CSS model for animation begins to break down when it comes to scalability. Animating a large number of elements simultaneously requires a great deal of heavy lifting by the browser, which will reduce the overall performance of the application.

Each time a DOM element is repositioned, which must happen multiple times per second for a smooth animation, the browser's layout engine requires a significant amount of overhead to recalculate and repaint the elements in the DOM hierarchy. Scale this up to tens, hundreds, or thousands of elements, and a sizable performance hit can be noticed even on modern computers.


Text on the canvas

Rendering text onto the screen is such a basic task for any website that we might take for granted the small gears that are turning in the background. When you want text on the screen, you simply type it between a couple of HTML element tags, possibly accompanied by a bit of CSS, and let the browser take over. This is not the reality, however, when it comes to canvas.

When you want to render text with canvas, a few basic tools are available. They allow for nearly all of the basic functions required to accomplish almost anything, but they don't provide much ease-of-use when developing a canvas application.

The basics

The canvas context object provides various properties that you can set when rendering text. It also provides a function to do the actual rendering. The properties include:

  • context.font

    Setting the value of context.font lets you control the font family, size, weight, and style of the text to be rendered. The value assigned is a single string with the various options pieced together and separated by spaces.

    The input format here is a bit of a hurdle. For instance, the font family must be provided in this string every time the value is updated. Listing 1 shows the setting for small font.

    Listing 1. Setting a small font
    context.font = 'italic 8px Arial';
    context.fillText('Variety is the spice of life!', 0, 50);

    Listing 2 shows the setting for a large font.

    Listing 2. Setting a large font
    context.font = 'italic 20px Arial';
    context.fillText('Variety is the spice of life!', 0, 50);

    A user cannot simply set the font family once and adjust other options at a later time depending on the text they wish to render to the screen. An approach to remedy this issue will be discussed later.

  • context.fillStyle

    context.fillStyle is used for various canvas operations; in the case of text, you set the value to control the color of the font to be rendered to the screen. The input format for the value specified conforms to that of CSS. The following are all valid input samples:

    • Basic colors: 'red', 'blue', 'green', and so on
    • Hex values: '#rrggbb'
    • 'rgb(r, g, b)'
    • 'rgba(r, g, b, a)'

    For example, to set the fillStyle use: context.fillStyle = 'red';.

  • context.fillText()

    Call this function to render text onto the canvas. It takes the following parameters:

    • (string) text: The text to draw onto the canvas.
    • (float) x: The x dimension at which to draw the text.
    • (float) y: The y dimension at which to draw the text.
    • [optional] (float) maxWidth: A maximum width to attempt to keep the text under. If possible, a more horizontally condensed font will be used, or even a smaller font.

Additional tools

An important additional tool you can use is a function of the context object called measureText(), which takes a single string parameter. The result is an object containing the measured dimensions of the provided string, as shown in Listing 3.

Listing 3. Determining width of a string of text
context.font = '30px Arial';
var dim = context.measureText(
    'Hello, world!'
);
 
alert(
    'width: ' + dim.width + '\n' +
    'height: ' + dim.height
);

Listing 3 will display an alert with an output similar to what is in Listing 4.

Listing 4. Alert
    width: 164
    height: undefined

Notice the resulting undefined for height. Strangely, all browsers will always return an undefined result, so it's effectively impossible to determine the exact text height using the measureText() function. There are a few techniques available to determine a fairly representative value. For many fonts, certain letters are quite square, such as the letter M. You can measure the width of one of these characters and use that value as an approximate measure for the height of the font.

Another method is to simply use the supplied size of the font as a base for the working height. If a value of 30px is supplied in the example above, you could add a few pixels for vertical padding and use the resulting value as the approximate height.

Building blocks

To increase the efficiency of the development process, you can use the previously mentioned tools to perform basic operations in a friendlier manner by creating a simple wrapper class. The class would automate the setting and swapping of the various context properties and ultimately call for the rendering of the desired text to the canvas. You can automate the issue with the context.font property. The example in Listing 5 was used in Part 1 of this series.

Listing 5. Rendering text with a dynamic style in canvas
context.font = '18px Arial';
context.fillStyle = 'green';
context.fillText('Variety', 0, 50);
context.translate(60, 0);  //move 60 pixels to the right (a)

context.font = '12px Arial';
context.fillStyle = 'blue';
context.fillText('is the', 0, 50);
context.translate(35, 0); //move 35 pixels to the right (b)

context.font = 'italic bold 12px Arial';
context.fillStyle = 'red';
context.fillText('spice of life!', 0, 50); // (c)

Figure 5 shows the three steps from Listing 5 for rendering text in canvas.

Figure 5. Rendering text with a dynamic style in canvas
Rendering text with a dynamic style in Canva

Using the raw canvas API, you had to write a fair amount of code. If you could simplify the code required to achieve the example above, it would increase the efficiency when rendering text to the canvas. For instance, instead of numerous lines of raw code, you could use the code in Listing 6.

Listing 6. A concept of workflow to augment canvas text rendering
var myText = new CanvasText();
myText
    .family('Arial')
    .size('18px')
    .weight('bold')
    .color('green')
    .append('Variety')
    
    .size('12px')
    .weight('normal')
    .color('blue')
    .append('is the')
    
    .style('italic')
    .color('red')
    .append('spice of life!')
    
    .render();

Figure 6 illustrates the code in Listing 6. The example uses chaining, which is a clean and simple syntax commonly used in jQuery and jQuery plugins.

Figure 6. Augmenting canvas text rendering
Augmenting Canvas text rendering

In this way, the process is simplified. Though there's nearly the same number of lines of code, the complexity of each line is greatly reduced. And, you no longer need to manually position each block of text with differing styles. If you decide later to adjust some of the properties of any of the blocks of text, you've removed the necessity of manually repositioning the following blocks.

Recall that the canvas.font property is a combination of multiple properties that decide how text will be rendered. Between the Variety and subsequent blocks, you didn't have to specify the font family. Between the 'is the' and 'spice of life!' blocks, specification of the size and weight of the font was also not required. A simple mechanism to remember the properties previously applied was employed here, thus alleviating the need to reconfigure desired properties of text that were previously applied. See Resources for a working example of the CanvasText class.

To achieve the results above, simply group the styling properties of the text you wish to render, specify the styling properties accordingly, then call the append() function of the CanvasText object that closes off the group and stores it for later use. When you render the text, loop over these groups and apply the styles as they're specified. As you iterate over the groups, keep a working history of the previous style state and override as necessary. These actions achieve the stylistic memory and reduce the required specification to render text onto the canvas. This is only one benefit of employing a wrapper class, or set of building blocks, to automate the process of rendering text to canvas.

Word wrapping

Word wrapping is something that many people might take for granted. When an HTML element contains content that is too long to fit on one line, it simply works. We don't have to consider the length of text, the width of the container, and so on. With canvas, however, it's not quite that simple. HTML canvas elements do not currently contain built-in functions to manage such things, so you have to programmatically create the functions using tools and methods previously mentioned.

For example, to have text wrap when it surpasses the width of a container, you must know the width of said container, the width of the text you wish to render, and the height of a line of text. You also need to create the virtual container and provide methods for specifying the width of that container. After you've created a process for doing this, you're ready to begin diving into the logic required to achieve word wrapping in canvas.

Listing 7 elaborates upon Listing 6 and provides some arguments to pass into the constructor of the CanvasText class.

Listing 7. Passing arguments into the constructor of the CanvasText class
var myText = new CanvasText(
    {x: 50, y: 50},
    {width: 100, height: 200}
);
myText
    .family('Arial')
    .size('18px')
    .weight('bold')
    .color('green')
    .append('Variety')
    
    .size('12px')
    .weight('normal')
    .color('blue')
    .append('is the')
    
    .style('italic')
    .color('red')
    .append('spice of life!')
    
    .render();

The additional arguments represent the x and y coordinates and dimensions of the position of the text to be rendered and the size of the container, respectively. Note that an explicit width has been specified to restrain the content wrapping.

Now that you have a container size to conform to, you can begin to create the functions to achieve the desired result. In addition to looping over each differing style, you'll also need to loop over each word to acquire some data on how wide the text would actually be once rendered. With this information, you can keep track of the width of the text you've already rendered and decide whether the next word will render outside the desired range. Fortunately, the measureText() function will provide exactly the information needed.

Listing 8 shows the code needed to implement word wrapping with the CanvasText class.

Listing 8. Including word wrapping functions
// use measure text 
var currentWordWidth = context.measureText(currentWord).width

// word wrap code here
if (textAdjustment.x + currentWordWidth > this._size.x || textToDraw == '\n') {
    textAdjustment.x = 0;
    textAdjustment.y += parseInt(previousFontOptions.size, 10);
}

The end result is shown in Figure 7.

Figure 7. Word wrapping result
Result

See Resources for a working example of the CanvasText class with word wrapping.

With a fairly simple wrapper class, you automated the styling and word wrapping tasks in canvas. This wrapper class can now be reused wherever you need to render text with canvas.


Tying it all together

We tend to take usable UIs for granted. From HTML to Flash, to Silverlight, they all offer an essential set of UI components in the form of text, menus, scrollbars, and form elements.

The next example is a design for the foundation for a simple space shooter game. You'll create: a spaceship component that can fly around the game area, a chat window, and a store. Some of the components require animation, and some provide textual information that might need to be updated or rendered frequently.

See Resources for the complete working example of the space shooter game.

HTML approach

The first step is to try to create each of the game components using basic HTML markup. With a little CSS knowledge, UI components such as the shop and chat system can be created fairly quickly.

Now for the fun part: the space ship. For the HTML implementation, create a simple DIV element, attach a background image for the ship, and add elements to display things such as the player's name and health. The code for these components thus far lies solely in the HTML and CSS of the example.

Listing 9 shows some of the CSS required for the ship and the text that displays underneath it. The position of the DIV with the player class is what you'll be updating later on in the DOM render function to achieve the animation.

Listing 9. CSS for styling of player's ship, name, and health
.player {
    position: absolute;
    width: 100px;
    height: 100px;
}

.text-under-ship {
    position: absolute;
    top: 100px;
    left: 0;
    width: 100px;
}

.name {
    font-family: Georgia;
    font-size: 15px;
    font-weight: bold;
    color: red;
}

.health {
    font-family: Georgia;
    font-size: 10px;
    color: yellow;
}

At this point you can start working with the code involved to handle the game logic, as shown in Listing 10. The code is largely centralized in the Game object of the JavaScript. It will be responsible for handling user input and calling the update and render functions of related components.

Listing 10. The gameLoop function used to drive the application
gameLoop: function() {

    // calculate time elapsed since last update
    var currentTime = new Date().getTime();
    var elapsed = currentTime - this._previousTime;

    // call updates
    Ship.update(elapsed);

    // call renders
    if (this._canvasRendering) {
        CanvasManager.render();
        Ship.renderCanvas();
    } else {
        Ship.renderDOM();
    }

    // store current time as the previous update time
    this._previousTime = currentTime;
}

A key piece of the example is the update functions of the Ship class. These functions are responsible for managing and updating the speed, direction, and position of the ship that will be used by the rendering code.

Until now, most of what you've created is reusable, regardless of the rendering approach. Listing 11 takes a deeper look into the differences between the DOM and canvas rendering functions.

Listing 11. HTML rendering function
renderDOM: function() {
    var player = jQuery('#player');

    player .css({
        left: this._position.x,
        top: this._position.y
    });

    var rotationTransform = 'rotate(' + (this._rotation / 100 * 360) + 'deg)';
    var ship = player.find('#ship')
        .css('transform', rotationTransform )
        .css('-webkit-transform', rotationTransform )
        .css('-moz-transform', rotationTransform )
        .css('-ms-transform', rotationTransform )
        .css('-o-transform', rotationTransform );
}

The code in Listing 11 is minimal because it uses jQuery to position the ship and set the rotation. For true cross-browser compatibility, various vendor-specific flavors of the transform property must be set.

The HTML approach ended up being relatively simple to implement, with little complexity, and only a small amount of code required. The disadvantage is that the potential for achieving an acceptable framerate is lacking due to the browser overhead in rendering DOM elements.

Canvas approach

Before working with canvas, you need a reference to the canvas object you'll be working with. After you have that, you need to initialize the context you want to work with. This is the context discussed earlier in this article and the means for working with a canvas element.

Employ a simple canvas manager class, as shown in Listing 12.

Listing 12. Canvas manager class
var CanvasManager = {
    canvas: null,
    context: null,
    _size: null,

    init: function() {
        this.canvas = document.getElementById('canvas-game-area');
        this.context = this.canvas.getContext('2d');

        this._size = {
            x: this.canvas.width,
            y: this.canvas.height,
        }
    },

    render: function() {
        this.context.clearRect(0, 0, this._size.x, this._size.y);
    }
}

In most applications, each time you want to update what's rendered on the canvas, you have to first clear what was previously rendered to gain a clean display area. You can achieve this with the clearRect() function, as shown in Listing 13.

Listing 13. Canvas rendering function
renderCanvas: function() {
    var context = CanvasManager.context;
    
    // save the context to prepare for our 
    // upcoming translation and rotation
    context.save();

    // translate the canvas to the ship's center position
    context.translate(
        this._position.x + 50, 
        this._position.y + 50
    );

    // rotate the canvas to show the angle the ship is pointing
    context.rotate(this.getRotationInRadians());

    // draw the ship with an offset of half the height
    // and width to center the image
    context.drawImage(
        this._displayImage, 
        -50,
        -50
    );

    // restore the context
    context.restore();

    this._playerName.render();
}

The code in Listing 13 shows some advantages and disadvantages of the canvas approach. The chief benefit is increased performance. The effect is exaggerated even more when you have more components to render, as shown in Figure 8.

Figure 8. Increasing number of ships to exaggerate performance differences
Increasing the number of ships rendered to exaggerate performance differences

The example is now rendering 50 ships layered on top of one another (see Resources for the working example). Clearly, the HTML version lags quite a bit. Switching to the canvas version dramatically improves the performance.

The amount of code for the two approaches was noticeably different. With HTML, you accomplished the positioning of the ship with one function call by using jQuery. With the canvas approach, you need a bit more.

In Listing 13 there's also an additional call to draw the player's name. This wasn't necessary in the HTML method because you were positioning an element that contained both the ship and the accompanying text. In canvas, though, you don't have this convenience. To achieve the rendering of the player's name, the example used the CanvasText class, which effectively moved all of the work for rendering the ship and its accompanying text out of the DOM and into canvas.


Conclusion

In this two-part series, you explored the criteria for choosing a canvas or HTML-centric architecture. In this article, you learned about animation considerations and how to overcome text rendering limitations. The examples showed how to bring the concepts together and provided insight into different approaches for hybrid canvas-HTML architectures.


Download

DescriptionNameSize
Article source codecanvashtmlpt2sourcecode.zip20KB

Resources

Learn

Get products and technologies

  • jQuery: Get the popular JavaScript Library that simplifies HTML document traversing, event handling, animating, and Ajax interactions for rapid web development.
  • Kibo: Another popular library specifically written for speedy cross-browser keyboard event handling.
  • IBM product evaluation versions: Download or explore the online trials in the IBM SOA Sandbox and get your hands on application development tools and middleware products from DB2, Lotus, Rational, Tivoli, and WebSphere.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development, Open source
ArticleID=831651
ArticleTitle=Complement canvas with HTML markup, Part 2: Animation and text rendering
publish-date=08282012