A Comprehensive Guide to Taking your HTML5 Game Offline

HTML5 offline storage logo

When you ask how to make your HTML5 game playable offline, you’ll often get a lot of varying opinions about how that can be accomplished and what the right technique is to use. But for the most part we can agree that there are one or two techniques that will work across all browsers. The point of this article is to explore all the possibilities for making a game playable offline, noting which methods will work for the majority of browsers as well as examining the strengths and weaknesses of each technique.

TL;DR: There are only really two techniques you should use to make your game playable offline: Web Storage (local storage) and Appcache. Use local storage to store only information and not to store your games files and assets (especially JavaScript). Use Appcache to cache your games files and assets, explicitly caching all of your files and assets into the manifest file upfront (you’re not going to be able to do much else).

To begin, we’ll take a look at each of the different ways that have been presented in the HTML5 specification to make an application accessible offline, as well as their current browser support. Using this information, we’ll make a decision about which method(s) to use and how to go about using them to take our HTML5 game offline. The decision of whether or not to use a particular method will mostly rely on current browser support.

I will not be going into too much detail about how any of these API’s work. Instead, I’ll link to relevant articles that go into great detail while I’ll just introduce them and their purposes. After we pick the method(s) to use, I’ll go into detail on how to use that method to offline a game.

First up on our list is the Filesystem & FileWriter API. The Filesystem API is a virtual filesystem that allows you to create, read, and write files to a section of your local filesystem. This would enable you to store your HTML, CSS, and JavaScript files so that the user would have access to them offline.

Let’s take a look at the current browser support for the API. All stats come from caniuse.com.

Filesystem API browser support

As you can see, the Filesystem API is only supported by about 35% of browsers with webkit browsers being the only supporter. This makes the Filesystem API a poor choice for making your game playable offline.

The next API is the Web SQL Database. The Web SQL Database is a client-side database that allows you to store information to the client in much the same way as a server-side database. This would enable you to store your game’s information, user information, and even client-side files like HTML and CSS if you so desired.

Web SQL Database browser support

Although the Web SQL API is supported by about 54% of browsers, the main problem is that the W3C has stopped maintaining the specification for it and is probably not going to pick it back up. So until they do, this is also a poor choice for making your game playable offline.

The third method is the IndexedDB. The IndexedDB is an indexed persistent storage method for storing JavaScript objects on the client. Because you are storing JavaScript objects, you would only be able to store data to the IndexedDB such as your game’s information and user information.

IndexedDB browser support

IndexedDB is supported by almost 59% of browsers with good support even in Internet Explorer. However, it lacks support in the mobile browsers, making this another poor choice for making your game playable offline.

Next up is the Application Cache. The Application Cache (or appcache for short) is a method for making your files available offline by telling the browser to cache them on the client. This would allow you to cache all of your HTML, CSS, and JavaScript files as well as your images and audio files for offline use.

Application Cache browser support

The appcache has a great browser support of 80% and even has mobile browser support. This makes it very good choice for making your game playable offline. However, it doesn’t allow you to store your information, which is where our final method comes in.

The last method is Web Storage. Web Storage (also referred to as local storage) is a method for storing key/value pairs of information to the user’s local storage. This would allow you to store your game’s information, user information, and any other data you wanted to store.

Web Storage browser support

As with appcache, Web Storage has a great browser support of almost 91% (even all the way back to IE 8) and is the recommended way of storing information to the browser. This information is even accessible while offline, so this is another great choice.

Now that we’ve looked at the different ways to allow you to offline a game, it’s obvious to see that appcache and Web Storage are the only viable choices because of their current browser support. This doesn’t really come as a huge surprise as these are the methods that most developers use to get offline access. With this in mind, let’s look at how to get the most out of these two methods.

To help illustrate the different ways to use each method, I’ll be using Cut the Rope as an example. I figured that if it could be made to work offline, then most games would be able to do likewise.

Ideally there are a few goals that should be met when storing the files and information your game needs to play offline. The first is that you should have control over what gets stored and when it gets stored. This means that if you want to have a game that stores each asset only when it’s requested or just want to store everything all at once, you should be able to do so.

The second goal is that you should be able to not have to worry about the size of the file or information you are storing. This would allow you to store everything you need for your game (after optimizing it of course) so that you wouldn’t have to leave anything out.

As it stands today, there is no “right way” to store all the files and information a game needs for use offline. As such, we’ll be taking a look at many different techniques to do so, as well as their advantages and disadvantages. But first, let’s outline some parameters using Cut the Rope.

Cut the Rope has over 100 images that are needed to play the game. Of these 100 images, 44 of them are encoded in base64. The game also incorporates about 30 audio files. In total, there are about 3998KB (3.9MB) of images and 686KB of audio. The main JavaScript file for the game is 273KB (minified) and the main CSS file is 45KB (minified).

To be able to have Cut the Rope playable offline, all of these files, as well as the user’s information about level progress, sound and language preferences, and how many stars collected for each level must be stored and accessible. Quite a daunting task.

Let’s take a look at each method in turn and see if we can get Cut the Rope playable offline. Remember, we are looking for both control over what gets stored and flexibility for file sizes. We also will be focusing on how to store a games assets (images and audio).

Web Storage is a great way to store information to the browser that can be accessed later, even offline. However, there are some disadvantages to using it. The first  is that you are only allowed roughly 5MB of storage. The second problem is that each key/value pair is stored as a string. This means that if you wanted to store boolean values or integers, you would have to use the JavaScript function parseInt() to get them back into their desired format.

The advantage for working with local storage is that you get programmatic control over what gets stored and when it gets stored. This makes it extremely flexible for any game and the ideal way to store your games information and user information. However, this information is limited to what you can fit into 5MB.

So how can we store the games files and assets using local storage? There are a few techniques available to use.

The first technique is to store all your HTML, CSS and JavaScript files as strings in local storage. This technique allows you to store only the files the user needs and only when the user needs them. For example, if you have JavaScript code that is only needed during certain levels of your game, then you wouldn’t need to locally store that file until the user got to those levels.

This technique presents a huge problem however. Because JavaScript must be stored as a string, the only way to get it back into usable JavaScript code is to call eval() on it. Using eval on code stored in local storage is a very bad idea as this can easily open up your game to cross-site scripting attacks. I highly recommend against this approach just for that reason alone.

Another approach to storing a JavaScript file would be to use JSON.stringify() on it and then store that into local storage. However, stringifying will remove all functions from the object, making it only good for storing data and not code.

This technique can be used to store HTML and CSS files to local storage, but you would have to rebuild them back into HTML and CSS.

The second technique is for storing assets. In this technique, you base64 encode all of your images and audio files and store the resulting strings into local storage. The advantage to this technique is that you can store only the images and audio files the user has used while playing online.

Unfortunately, this technique is very limited for two reasons. The first reason is that encoding in base64 will increase the file size by about 37%, meaning you can store even less into the local storage. If we were to try to base64 encode all of Cut the Rope’s assets, it would total about 6308KB (6.1MB), which is more than we are allowed to store.

The second reason that this technique is limited is that base64 encoded images are about 6x slower on mobile devices. This is because the image must be decoded back into its binary representation before it can be displayed. For this reason, you should be careful about which images you do encode and mainly use them for small files and CSS sprite sheets.

Web Storage is great for storing information about the game such as level data, user preferences, where the user stopped playing, and any other information about your game or user that you wanted to store. Web Storage is not great for storing your games files and assets. For this, we must turn to the appcache.

The appcache is the goto method for storing your game’s files and assets offline. It allows you to explicitly cache everything your game needs to work while offline. At first this sounds great, but it comes with a price: appcache is a pain to work with.

I won’t go into details about why this is the case as other articles have already done that. Instead, I want to focus on how to use the appcache for offline play.

The main problem with appcache is the lack of control you have when things get stored. Everything your game needs must be cached upfront and once there, it cannot be added or removed without modifying the cache manifest. This means that you cannot gradually store assets for your user but must instead download them all on page load, causing the browser to load slightly slower on first viewing.

Another problem appcache presents is serving the correct assets for your user based on what the user is able to use. For example, Internet Explorer, Safari and Chrome can play .mp3 audio files, but Firefox and Opera use .ogg files, and almost all of them can play .wav files. And what if you wanted to serve smaller images based on whether the user is on a mobile or tablet device? This is hard to accomplish using appcache.

There are four techniques for working with the appcache.

The first technique is by far the most common. This technique means that you store every single file and asset your game needs in the manifest. This is the safest route to take and probably is good enough for most games. If you don’t need too much control over your files or assets, use this technique.

If Cut the Rope were to use this approach just for desktop browsers, they would have to store both .mp3 and .ogg audio files in the manifest. This makes it a lot harder to maintain the manifest if an audio file needs to be updated or removed. If we included responsive images to the requirement, there would have to be almost double the number of images in the manifest, bringing it to a total of about 260 assets in the manifest file.

Thus, for Cut the Rope, this technique just doesn’t cut it (pun intended).

The second technique is not very well known but is very powerful. Most people assume that the manifest file must be a plain text file. But the truth is that the manifest file can be any file type you want so long as the header returned to the browser is Content-Type: text/cache-manifest. This means that if you made the manifest file a .php file, you could have server-side control over what gets put into it.

This control would allow you to selectively put the correct assets into the manifest per user and not have to load all assets for every user. For example, let’s say we wanted to only cache .mp3 files in our manifest. We could include in the manifest file the following.

header("Content-Type: text/cache-manifest");

$dir = new RecursiveDirectoryIterator(".");

foreach(new RecursiveIteratorIterator($dir) as $file) {
if ($file->IsFile() && $file != "./manifest.php" && substr($file->getFilename(), 0, 1) != ".") {
if(preg_match('/.mp3$/', $file)) {
echo $file."\n";
}
}
}

This code is great because it will recursively search the specified directory for all files ending in a .mp3 extension and output them to the manifest. This allows the manifest to be highly maintainable as it will dynamically add the needed files to the manifest.

This technique would also allow you to serve the correct resource for each browser. Although browser detection is considered bad practice, I think when used in this context it can be beneficial to the user. So long as you provide fallbacks for browsers not specifically targeted, I think you should be ok. What’s even better is you can use feature detection on the server to detect if the feature is supported for a user or not. Examples of server-side feature detection include Modernizr Server and Detector. See this article for more details on server-side feature detection.

This means that we could serve .mp3 files to those browsers we know support them, .ogg files to their supported browsers, and as a fallback serve both types. Assuming we knew the current browser, we could include in the manifest file the following.

$mp3_browsers = ["Chrome", "Internet Explorer", "Safari"];
$ogg_browsers = ["Firefox", "Opera"];

if (in_array($current_browser, $mp3_browsers)) {
// output all .mp3 files
}
else if (in_array($current_browser, $ogg_browsers)) {
// output all .ogg files
}
else {
// output both .mp3 and .ogg files
}

This technique would also allow you to serve the correct image sizes for your user. But again, make sure you provide proper fallbacks for when you miss a browser don’t support a feature.

A huge problem with this technique is that it will put a lot of strain on your server as it must scan and output files every time someone requests the manifest file (which is every time the page loads). I’m sure there is a lot of room for improvement though to fix this, but I wouldn’t recommend this technique for most developers.

Using this technique, Cut the Rope could have a maintainable manifest file as well as control over what files each user caches. But the server strain on their system would make this technique inadequate for their needs as they would have to dynamically generate the cache manifest per user.

This next technique is very similar to Selective Caching but will create less strain on the server. We still want to have control over what the user caches, but we don’t want to have to generate the manifest for every user on every page load.

Instead we create several different cache manifests, one for each type of device/browser we are trying to target (chrome-mobile, chrome-tablet, chrome-desktop, firefox-mobile, etc.). Then, at the top of the main page, we detect these settings and fill in the manifest attribute of the html element with the proper manifest.

<?php
$manifest;
if ($browser == 'Chrome') {
if ($size == 'mobile')
$manifest = 'chrome-mobile.manifest';
else if ($size == 'tablet')
$manifest = 'chrome-tablet.manifest';
else
$manifest = 'chrome-desktop.manifest';
}
// Other browsers
else
$manifest = 'all.manifest'
?>

<html manifest="<?php echo $manifest ?>">

With this technique, Cut the Rope could have control over the cache of the user without the server strain. Because of this, I think this is the technique that Cut the Rope might use to have their game playable offline.

It is important to remember that this technique requires browser detection, so you must provide a fallback to a manifest that caches everything for browsers you do not target. Remember that you can use feature detection on the server to know which manifest you should server to which user.

The last technique is one I came up with and thus can still be refined. This technique is extremely advanced and I do not recommend it for most developers. However, it’s pretty cool if I do say so myself so I wanted to share it.

The idea is that the appcache can be programmatically updated with new assets and files when needed and updated accordingly. This would prevent the need to declare every asset and file in the manifest up front as well as allow you to use client-side feature detection to know what you should put into the users manifest (rather than browser detection).

To make this work, we have to create a unique manifest file for each user. This means that there will be lots of manifest files stored on your server. This is necessary so that we can update the manifest with new assets when needed on a per user basis.

I thought about storing all the information into a database and then populating a manifest file for each user, but then there wouldn’t be any way to update that manifest uniquely for each user. If every user looked at the same manifest file, one change would cause all users to download that manifest with that change.

The first thing we do is create a manifest file that only has the basic files we need to get our game running. For Cut the Rope, this would just be the JavaScript and CSS file and none of the images or audio files (except the ones that will be served to all user regardless of browser, if any). This will be the base file for all new manifest files generated for each user.

When a user first accesses the game, we will generate a unique string/id for that user. We then use this identifier to create a new manifest file for that user and set the manifest attribute of the html element to use that manifest.

<?php
$manifest = 'manifest_'.$some_unique_identifier;
$contents = file_get_contents($base_manifest_filename);
$create = fopen($manifest, 'w');
fwrite($create, $contents);
?>

<html manifest="<?php echo $manifest ?>">

We next create a PHP file that will be used to write new assets to the unique manifest. The idea is that this file can be called from the client using an AJAX request, sending along any relevant information such as the manifest file name, if we can use .mp3 files or .ogg files, if the user is on a mobile device or a desktop, and even new assets to insert into the manifest because the user accessed a new level.

When called, the PHP file would parse the parameters of the query string given from the client and then use them to append new assets to the users unique manifest. Once the client received confirmation that the write was successful, JavaScript could then be used to check for new updates of the manifest file and have the client download all files in the new manifest, including the newly inserted assets!

I’ve tested this on a very small scale and it seems to work well, but I’m not sure how well it will scale. However, I think if done right it could potentially change how large games use appcache. Please feel free to discuss the technique in the comments.

A difficulty I foresee is keeping track of which user owns which manifest and making sure they always point to that manifest. One solution you could use is force users to register to play your game so that you can keep track of their unique identifier.

Again, this technique was an experiment and I do not recommend it for most developers looking to make their game playable offline.

Appcache, although a pain to work with, can help make your game playable offline. When using appcache, most of the time you should place all of your files into the manifest as it is currently very hard to create dynamic manifests.

There are many methods and techniques that could be used to make your HTML5 game playable offline. However, only local storage and the appcache are supported across most browsers, making them the only viable methods.

Local storage should only be used to store information and should not be used to store files or assets. There are always exceptions to the rule (such as storing CSS or HTML files), but know that you have a limited amount of space to work with. You shouldn’t use local storage to store JavaScript as this is a huge security vulnerability. Storing images and audio into local storage requires that you base64 encode them first which can slow down mobile devices and increase the file size of each asset.

Appcache should be used to store all of your games files and assets. Because of the difficulty and current implementation of appcache, it is recommended that you explicitly cache all of your file and assets in the manifest upfront. This is the easiest technique and with the least amount of pain associated with it.

I hope you have found this guide helpful. Please let me know if I missed any techniques or if you just want to give feedback.