As developers we love building cutting-edge web apps for state-of-the-art browsers. Those are, fortunately, nowadays used be a vast majority of users (around 95% of all users in our 10 most recent projects). So for our newest project, we decided focusing on new technologies like HTML5 Canvas and the File API, among others. In this two-part article, we want to share our experiences using these techniques while building the Facebook app "tele.ring Poster-Creator".
This is part 2 of the "New Adventures In HTML5" series. Be sure to read part 1 before continuing.
We first check whether the browser supports the File API (the parts we care about are File and FileReader). If it does and the user has picked a file using the file input, we create a new FileReader and read the first file into a data-URL (only one of several possible output formats). We also attach an event listener to the FileReader which fires as soon as it has finished reading the file.
If you paid attention earlier, you possible already know why we chose to read the image into a data-URL: The last example from the Canvas part of this article shows you what the importImage function basically looks like: It takes the URL of an image, downloads it, and draws it onto the canvas. With data-URLs, the downloading part is skipped by the browser, enabling us to immediately draw it onto our canvas. All of this is possible without changing anything in the importImage function from earlier.
Lets face it: Even though importing images is lightning fast thanks to the File API, the user still has to click on a file input (which at least looks like a button in our app), clicking through the file system dialog that pops up, then picking the desired image and clicking again. There is just too much clicking going on! While this is the way some users also pick files on their computer, many prefer to simply grab a file from the desktop, dragging it into the program where they want to use it and dropping it there. This is, essentially, only one click, which is beautiful!
In this example we make the entire document a drop target, so the user can just drop the file anywhere on the page. In the first "if", we check whether the browser supports data transfer. If it does, we check whether there are any files to be imported. If so, we take the first one and import it. The function importFromFile() is very similar to the previous code example. The last lines prevent the browser from simply displaying the image instead of importing it.
Keep in mind that you should also give the user visual feedback when and where to drop the file. Fortunately, there is also an event that fires as soon as a file is dragged into a drop zone, but before it is dropped. The event is called "dragenter". Keep in mind that you should only display your visual feedback if the browser supports working with the file. IE9, for example, does support drag & drop events, but it does not support the File API.
There is a third event called "dragover" which fires repeatedly when dragging a file. You normally don't need this, but you should attach an event handler to it anyway to prevent the browser from behaving in unexpected ways (for example when selecting text on the page and dragging that around). Just call e.preventDefault() and e.stopPropagation() inside the handler and you're good.
Being able to render text on a canvas is great, but what if the client wants to use their CI font?
What we are doing instead: We simply ignore the fact that the font is not loaded and pretend it is. Canvas does not care and just uses a default font until the real one is ready. On the next redraw (which might be just some milliseconds away), everything looks perfect.
To shorten (or rather: get rid of) the "flash of the default font", we pre-load the web font on the app's landing page even though we are not using it there, so that the font is already in the browser cache before the user even gets to the canvas. That way, Canvas can immediately render its text in the right font.
While IE9 is a rather decent browser and supports almost every technology mentioned in this article, it does not support the File API. This means that if the users clicks the aforementioned upload button, we actually have to upload the file. Unfortunately, there is no easy way to upload a file from IE9 via AJAX, and we wanted to find a solution that required a minimum amount of IE9-specific code.
The solution we came up with only added a couple of lines of code to the app's source and is using another rather new web technology: Local Storage.
Local Storage allows you to save data in the browser that is still there after the page refreshes. In that way, it is similar to cookies, but easier to handle and it allows us to store much more data. It is accessible via the variable localStorage and has three important methods: setItem(), getItem and removeItem(). Keep in mind that you can only store Strings in local storage, so if you want to store objects, you have to serialize them first, using JSON.stringify().
Here's what we are doing: As soon as the picture upload (which is a form submission) is initialized, we stringify the state of the app (i.e. the scale and rotation of the user image and the entered text) and store it with Local Storage. When the upload is finished, the form submission triggers a reload of the page, after which we load the state from Local Storage and apply it to the app.
So now that we have taken care of IE9, what about IE8? Well, in order to really support IE8, we would have had two options: Either dumb the app down, making it less awesome for everyone, or create a separate app only for older Internet Explorers, which would have made the app much more expensive. Since according to Google Analytics none of our Facebook apps that were active in the last couple of months had more than 5% IE8 users (the apps for this particular client only had about 2%!), this would not have made any sense.
So the thing to do was to redirect IE7 and IE8 users to a page explaining that their browser does not support the app, but there was a solution: installing Chrome Frame.
The user does not need admin privileges on the computer to install it.
The content string "chrome=IE8" tells the browser to only enable Chrome Frame if it is an Internet Explorer 8 or older. The ironic thing is: Since we do support the native IE9, the app will perform much better in IE7 with Chrome Frame enabled than it does in IE9.
Just one more thing: We also tested the app in the preview version of Internet Explorer 10 and were delighted to see that the app worked flawlessly! There is a bright future ahead of us, fellow web developers!