How to Create a Visual Library of Images in HTML5 Canvas
by David Catuhe
As a user interface fan, I could not miss the opportunity to develop with HTML5 Canvas. It unlocks a whole new set of ways to visualize images and data on the web. In this tutorial, I’ll walk you through how to create one for your site.
We will produce an application that will let us display a Magic the Gathering © (courtesy of www.wizards.com/Magic) cards collection. Users will be able to scroll and zoom using the mouse (like Bing Maps, for example).
Note: Image and data visualization is hardware intensive. Learn about HTML5 hardware acceleration and why it’s important here.
You can see the final result here: http://bolaslenses.catuhe.com
The project source files can be downloaded here: http://www.catuhe.com/msdn/bolaslenses.zip
Cards are stored on Windows Azure Storage and use the Azure Content Distribution Network (CDN : a service that deploys data near the final users) in order to achieve maximum performances. An ASP.NET service is used to return cards list (using JSON format).
To write our application, we will use Visual Studio 2010 SP1 with Web Standards Update. This extension adds IntelliSense support in HTML5 page (which is a really important thing Description: Sourire).
Debug with Visual Studio 2010
Debug with Internet Explorer 9 (F12/Developer bar)
So, we have a modern developer environment with IntelliSense and debug support. Therefore, we are ready to start and first of all, we will write the HTML5 page.
The HTML5 page
Our page will be built around an HTML5 canvas which will be used to draw the cards:
If we dissect this page, we can note that it is divided into two parts:
The header part with the title, the logo and the special mentions
The main part (section) holds the canvas and the tooltips that will display the status of the application. There is also a hidden image (backImage) used as source for not yet loaded cards.
To build the layout of the page, a style sheet (full.css) is applied. Style sheets are a mechanism used to change the tags styles (in HTML, a style defines the entire display options for a tag):
Thus, this sheet is responsible for setting up the following display:
Style sheets are powerful tools that allow an infinite number of displays. However, they are sometimes complicated to setup (for example if a tag is affected by a class, an identifier and its container). To simplify this setup, the development bar of Internet Explorer 9 is particularly useful because we can use it to see styles hierarchy that is applied to a tag.
Once the selection is done, we can see the styles hierarchy:
Thus, we can see that our div received its styles from the body tag and the .tooltip entry of the style sheet.
Our page provides a graceful degradation as it still works (with no annoying visual difference) when the browser does not support all the required technologies.
Now that our interface is ready, we will take a look at the data source to retrieve the cards to display.
The server provides the cards list using JSON format on this URL:
It takes one parameter (colorString) to select a specific color (0 = all).
Thus, in our case to connect to the URL of our server and get the cards list, we could go through a XmlHttpRequest and have fun to parse the returned JSON. Or we can use jQuery Description: Sourire.
So we will use the getJSON function which will take care of everything for us:
As we can see, our function stores the cards list in the listOfCards variable and calls two jQuery functions:
slideToggle that hides (or shows) a tag by animating its height
The listOfCards list contains objects whose format is:
Path: relative path of the card (without the extension)
It should be noted that the URL of the server is called with the “?jsoncallback=?” suffix. Indeed, Ajax calls are constrained in terms of security to connect only to the same address as the calling script. However, there is a solution called JSONP that will allow us to make a concerted call to the server (which of course must be aware of the operation). And fortunately, jQuery can handle it all alone by just adding the right suffix.
Once we have our cards list, we can set up the pictures loading and caching.
Cards loading & cache handling
The main trick of our application is to draw only the cards effectively visible on the screen. The display window is defined by a zoom level and an offset (x, y) in the overall system.
The overall system is defined by 14819 cards that are spread over 200 columns and 75 rows.
Also, we must be aware that each card is available in three versions:
High definition: 480x680 without compression (.jpg suffix)
Medium definition: 240x340 with standard compression (.50.jpg suffix)
Low definition: 120x170 with strong compression (.25.jpg suffix)
Thus, depending on the zoom level, we will load the correct version to optimize networks transfer.
To do this we will develop a function that will give an image for a given card. This function will be configured to download a certain level of quality. In addition it will be linked with lower quality level to return it if the card for the current level is not yet uploaded:
An ImageCache is built by giving the associated suffix and the underlying cache.
Here you can see two important functions:
So to handle our 3 levels of caches, we have to declare three variables:
Selecting the right cover is only depending on zoom:
To give a feedback to the user, we will add a timer that will manage a tooltip that indicates the number of images currently loaded:
Again we note the use of jQuery to simplify animations.
To draw our cards, we need to actually fill the canvas using its 2D context (which exists only if the browser supports HTML5 canvas):
The drawing will be made by processListOfCards function (called 60 times per second):
This function is built around many key points:
If the cards list is not yet loaded, we display a tooltip indicating that download is in progress:
Subsequently, we define the position of the display window (in terms of cards and coordinates), then we proceed to clean the canvas:
Then we browse the cards list and call the drawImage function of the canvas context. The current image is provided by the active cache (depending on the zoom):
And finally, we need to compute the number of frames per second:
Drawing cards relies heavily on the browser's ability to speed up canvas rendering. For the record, here are the performances on my machine with the minimum zoom level (0.05):
The site even works on mobile phones and tablets as long as they support HTML5.
To browse our cards collection, we have to manage the mouse (including its wheel).
The onmousemove event is connected to the canvas and used to move the view:
This function (onMouseMove) calculates the current position and provides also the previous value in order to move the offset of the display window:
Note that jQuery also provides tools to manage mouse events.
We can see that everyone does what he wants.
The function to register with this event is:
And we will use this function to change the zoom with the wheel:
Finally we will add a bit of inertia when moving the mouse (and the zoom) to give some kind of smoothness:
This kind of small function does not cost a lot to implement, but adds a lot to the quality of user experience.
Also to provide a better user experience, we will save the display window’s position and zoom. To do this, we will use the service of localStorage (which saves pairs of keys / values for the long term (the data is retained after the browser is closed) and only accessible by the current window object):
To add even more dynamism to our application we will allow our users to double-click on a card to zoom and focus on it.
Our system should animate three values: the two offsets (X, Y) and the zoom. To do this, we will use a function that will be responsible of animating a variable from a source value to a destination value with a given duration:
The use of this function is:
The advantage of the AnimationHelper function is that it is able to animate as many parameters as you wish (and that only with the setTimer function!)
Finally we will ensure that our page can also be seen on tablets PC and even on phones.
Here we see that if the screen width is less than 480 pixels, the following style sheet will be added:
This sheet will reduce the size of the header to keep the site viewable even when the browser width is less than 480 pixels (for example, on a Windows Phone):
This kind of development is also simplified by the use of frameworks like jQuery.
To go further
About the Author
David Catuhe is a developer evangelist for Microsoft France in charge of user experience development tools (from XAML to DirectX/XNA and HTML5). He defines himself as a geek and likes coding all that refer to graphics. Before working for Microsoft, he founded a company that developed a realtime 3D engine written with DirectX (www.vertice.fr)."