Building an Instagram clone - Part 1

Introduction

When I started out on this app I was only really just interested in seeing if the web platform had really evolved to a point where an app like the hugely popular Instagram app could be built using just HTML, JavaScript and CSS. As it turns out we can in fact do exactly that. This article walks you through the technologies that make this possible and shows how it is entirely feasible today to build interoperable web applications that provide a great user experience no matter what brand of browser the user is running.

If you happen to be one of the two or so people who have not heard about Instagram then you might be pleased to hear that it is a hugely popular photo sharing and social networking service that allows you to take pictures, apply interesting digital filters on them and share them with the world to see. The service got so popular that it was acquired by Facebook for a bag full of cash and stock in April of 2012.

InstaFuzz is the name of the app I put together and while I don't expect to be acquired by Facebook or anybody else for a billion green it does however make the case that an app such as this one can be built using only standards compliant web technologies such as Canvas, File API, Drag/Drop, Web Workers, ES5 and CSS3 and still manage to run well on modern browsers such as Internet Explorer 10, Google Chrome and Firefox.

About the app

If you'd like to take a look at the app, then here's where it is hosted at:

http://blogorama.nerdworks.in/arbit/InstaFuzz/

You can download the source and run locally from here. While this is a Visual Studio 2012 project there really isn't any server code or anything like that. You can use your favorite editor to look at the source and run it from the file system if you are so inclined.

As soon as you load it up, you're presented with a screen that looks like this:

clip_image002

The idea is that you can load up a photograph into the app either by clicking on the big red "Add" button on the bottom left hand corner or drag and drop an image file into the blackish/blue area on the right. Once you do that you get something that looks like this:

clip_image003

You'll note that a list of digital filters are listed on the left of the screen showing a preview of what the image would look like if you were to apply the said filter. Applying a filter is a simple matter of clicking on one of the filter previews on the left. Here's what it looks like after applying the "Weighted Grayscale" filter followed by a "Motion Blur". As you can tell filters are additive - as you keep clicking on filters, they are applied on top of what was applied earlier:

clip_image004

Let's next take a look at how the UI layout has been put together.

UI Layout

The HTML markup is actually so little that I can actually reproduce the contents of the BODY tag in its entirety here (excluding the SCRIPT includes):

<header>
    <div id="title">InstaFuzz</div>
</header>
<section id="container">
    <canvas id="picture" width="650" height="565"></canvas>
    <div id="controls">
        <div id="filters-list"></div>
        <button id="loadImage">Add</button>
        <input type="file" id="fileUpload"
               style="display: none;"
               accept="image/gif, image/jpeg, image/png" />
    </div>
</section>

<!-- Handlebar template for a filter UI button -->
<script id="filter-template" type="text/x-handlebars-template">
    <div class="filter-container" data-filter-id="{{filterId}}">
        <div class="filter-name">{{filterName}}</div>
        <canvas class="filter-preview" width="128" height="128"></canvas>
    </div>
</script>

There's nothing much going on here. Pretty much everything should be standard fare. I will however draw attention to the fact that I am using the Handlebars JavaScript templating system here for rendering the markup for the list of filters on the left of the screen. The template markup is declared in the HTML file (the SCRIPT tag in the snippet shown above) and then used from JavaScript. The template markup is then bound to a JavaScript object that supplies the values for handlebars expressions such as {{filterId}} and {{filterName}}. Here's the relevant piece of JS from the app with a bit of DOM manipulation help from jQuery:

var templHtml = $("#filter-template").html(),
    template = Handlebars.compile(templHtml),
    filtersList = $("#filters-list");
var context = {
    filterName: filter.name,
    filterId: index
};

filtersList.append(template(context));

As you can tell from the HTML markup all the filter preview boxes feature a CANVAS tag as does the big box on the right where the final output is rendered. We'll go into a bit more detail later on in the article as to how canvas technology is used to achieve these effects.

The app also uses CSS3 @font-face fonts to render the text in the header and the "Add" button. The fonts have been taken from the excellent Font Squirrel site and here's what the declaration looks like:

@font-face {
    font-family: 'TizaRegular';
    src: url('fonts/tiza/tiza-webfont.eot');
    src: url('fonts/tiza/tiza-webfont.eot?#iefix')
           format('embedded-opentype'),
         url('fonts/tiza/tiza-webfont.woff') format('woff'),
         url('fonts/tiza/tiza-webfont.ttf') format('truetype'),
         url('fonts/tiza/tiza-webfont.svg#TizaRegular') format('svg');
    font-weight: normal;
    font-style: normal;
}

This directive causes the user agent to embed the font in the page and make it available under the name assigned to the font-family rule which in this case is "TizaRegular". After this we can assign this font to any CSS font-family rule like how we normally do. In InstaFuzz I use the following rule to assign the font to the header element:

font-family: TizaRegular, Cambria, Cochin, Georgia, Times,
   "Times New Roman", serif;

You might also have noticed that there is a subtle shadow being dropped on the page by the container element.

clip_image001[4]

This is made possible using the CSS3 box-shadow rule and here's how it's used in InstaFuzz.

-moz-box-shadow: 1px 0px 4px #000000, -1px -1px 4px #000000;
-webkit-box-shadow: 1px 0px 4px #000000, -1px -1px 4px #000000;
box-shadow: 1px 0px 4px #000000, -1px -1px 4px #000000;

This causes the browser to render a shadow around the relevant element. Each comma separated section in the value specifies the following attributes of the shadow:

  1. Horizontal offset

  2. Vertical offset

  3. Spread distance - positive values have the effect of softening the shadow

  4. Shadow color

One can specify multiple shadow values separated by comma as in fact has been done above. Note that I've also specified the shadow using vendor prefix syntax for Firefox and Chrome/Safari using the moz and webkit prefixes. This causes the shadow to continue to work in versions of those browsers where support for this capability was provided using the vendor prefixed version of the rule. Note that the W3C version of the rule - box-shadow - is specified last. This is done deliberately to ensure that in case the browser supports both the forms then only the W3C behavior is actually applied to the page.

One often finds that web developers either fail to include vendor prefixed version of a given CSS3 rule for all the browsers that support that rule and/or fail to include the W3C version as well. Often developers just put the webkit version of the rule ignoring other browsers and the W3C standard version. This causes two problems - [1] poor user experience for users who are using non-webkit browsers and [2] it ends up resulting in webkit becoming a de-facto standard for the web. Ideally we want W3C to be driving the future of the web and not one specific browser implementation. So here are some things to remember when playing with experimental implementations of CSS features:

  1. Use vendor prefixed versions of CSS rules by all means but remember to specify the rule for all supported browsers and not just the one that you happen to be testing the page in (if you're using Visual Studio to edit your CSS then you might be interested in the supremely excellent extension for Visual Studio called Web Essentials that makes the job of managing vendor prefixes about as simple as it can possibly get).

  2. Remember to specify the W3C version of the rule as well.

  3. Remember to order the occurrence of the rules so that the W3C version shows up last. This is to allow clients that support both the vendor prefixed version and the W3C version to use the W3C specified semantics for the rule.

That's all for now. In the next and final post in this series we'll take a look at how the app supports drag/drop of files, the use of File API, how the filters themselves work and how we prevent the UI thread from freezing by delegating the core number crunching work to web workers.

comments powered by Disqus