Aaron Saray

My Blog

: Technical and Business knowledge, with less grammar correctness than my books.


HTML5, CSS3, Javascript ONLY Photobooth with Image Download

So I decided that I wanted to challenge the concepts I know about online photo booths. What are those?
a) they must be flash
b) they must have a server component
c) they work in all browsers (hhahaah – this example only works in Chrome at the moment).

What am I talking about? An online photobooth, with mimimum javascript, HTML5, CSS, minimal images, that accesses your webcam and allows you to download your creations. Yup, take a shot of yourself, and maybe drag some sunglasses onto it or something. Want to see what I’m talking about? Check it out here: Online PhotoBooth.

Now, let’s check it out.

Goals

My goals were simple:
First Screen

  • No flash / use HTML5 only
  • Minimal amount of javascript: don’t you DARE import jQuery if you don’t need to
  • Minimal css
  • Somewhat responsive: spoiler alert, I gave up before I tested this – I started out ok though.
  • Allow users to filter and add fun overlays to a webcam capture
  • Download the finished product without hitting a backend server/prompting the user to right click.

Did I accomplish them? Heck yeah I did. Let’s see what’s up.

HTML5 Document

The first part of the HTML document is as follows:

First, the class of modal and needAllow are on the body. The modal that is used for this application is purely css. It activates/deactivates based on the classes on the body. We’ll look at that more in the css and the javascript. Next of course is the header. I have this separate because I’m not going to blur out the header with the modal – just the body content.

ScreenshotFollowing that is the video. You’ll see that the video element is set to autoplay. If this is not true, when you send webcam to it, it will still require you to click ‘play’ to show the content. Below that is a hint to take a screenshot. When the user hovers over the video with the mouse, the icon changes to a small camera, and a click handler watches for a click. You’ll see that in the css and javascript.

Finally, the video filters are input buttons of type radio. This allows CSS3 filters to be applied to the streaming video. As a bonus, this same filter setting is applied to the screenshot.

Special note: Somehow, I totally didn’t think about the fact that the filters are actually on top of the element and not applied to the canvas image itself (referenced later). So the only real big unfished bug is the fact that a downloaded image does not have the filter applied to it. I’d actually have to edit the pixels of the image.

The next section of the HTML document has the content for when a screenshot is taken. Obviously, this is hidden at load:

The canvas element is where the captured screenshot will go. Then, the input field allows the user to name the file before they download it. If its not changed, the placeholder content is used for the filename. The download button allows the user to download. At the very bottom, the closeScreenshot link is put in the top righthand corner to close the screenshot. Fun fact: it also resets all the content on this page when that happens.

DecorationThe screenshot decoration div allows for a list of draggable items that can be placed on the canvas. You’ll notice its using the HTML5 draggable attribute. This allows drag handlers to be written so that no additional javascript libraries are required. I initially wanted the image to also be contentEditable – but in Chrome, there is a bug that does not allow images to get resize handles when applied this. Most editors have a work around for that. Instead, I added a mousewheel handler to this to handle resizing. More on that later.

Finally, the HTML document ends with the following two message divs:

The first is the allow message that hovers over the top of everything when the class needAllow is on the body. This lets the user know that they need to click the allow button on the browser. Additionally, the ‘up arrow’ is just a div on this screen. The red arrow will be created using only CSS.

The browser required div is for times when the browser is not identified as Chrome OR it is chrome, but an older version, and one of the required features does not exist. You’ll notice the chrome logo is embedded. I didn’t want to hit the server for this very small image.

The CSS

Now, let’s take a look at the CSS.

The styles here are mainly just grabbed from HTML5 boilerplate – I stripped out all the stuff that I wasn’t using. This isn’t that exciting. Let’s look at application specific css next:

This is just the setup for the site. Nothing special here.

The video container information is defined. The video is set up to have a small image of a webcam as the background before it is initialized with streaming content. The video itself has a small image of a camera icon as the pointer when you hover over it – as kind of a reminder that you can take a screenshot. The screenshot hint is designed to appear below the video – and only appears on hover.

This is the css for the allow message. Basically, it is not displayed unless the needAllow class is above it. Then it positions itself absolutely centered and near the top. The modal css uses webkit filters to gray out the content, blur it, and then shrink it a tiny bit. This gives a good 3d modal look. Finally, the up arrow is using CSS to create a triangle shape using borders. After it, another rectangle is added to be the base of the arrow. There was no need to actually have this in the HTML using this technique.

Now, chrome message CSS is pretty simple. Show it on top if its required.

Next, the filters:

Here I am not displaying the filters until the user has access to the camera. Then, the video filters are basically a set of radio buttons. I don’t want more than one filter at a time. Finally, the other css there is a combination of styles from twitter bootstrap and somewhere else that basically set the labels as ‘buttons’ for the radio input buttons. When a radio button is checked, the label looks selected.

Now, moving on to the screenshot CSS.

The screenshot window is not shown by default. It’s loaded when a screenshot is taken. Its positioned on top of the content – and the body modal class is added back. At any rate, the close screenshot link is placed in the corner of the screenshot window. The only other interesting css is the button.downloaded class which adds a checkmark after the content of the button if someone downloads something (and the css class is obviously added.)

Finally, lets look at the dragable decorations CSS.

So I kind of cheated – I was getting tired – this only took me about a day… and I knew I’d only have sunglasses. But basically, I just made a gutter for the decorations, and absolutely positioned the one that I have. Nothing big.

That’s it – now lets look at the javascript. I promise, for what it is..s it’s small!

Javascript

Remember, the goal was to use the least amount of javascript. I’m going to start a little bit different than normal – I’m going to start at the bottom of the document.

First, a Javascript base64 to ByteArray function (because you can’t do this natively) – got this from a github entry.

This is important for later: we need to create a base64 from canvas image into a ByteArray to pass to an HTML5 blob.

And here we go: On load, check to see if this is chrome. If its not warn them. Either way, continue on.

The only thing noteable of course is that I’m going to scope the creation of my videoStream inside of the anon function. Alrighty, let’s move on:

The comments make it pretty simple. I’m creating the namespace and adding a few helper functions. Next, here is the constructor and ALL of the methods. Don’t worry, I’ll go through them – but I thought splitting it up would make it more confusing.

Whew! Let’s start with initCam(). Note: this is the only public method in this whole object/class. First, I get the getUserMedia function and the window.URL pointer. These differ between HTML5 compatible browsers. (Yes I know I’m only supporting Chrome at this time, but I wanted to make it future compatible if I expand this functionality.) It continues – if I don’t have getUserMedia() access, it shows the warning and quits. We can’t move on from here anyway!

The next call is to getUserMedia() and asks for access to the video stream. The callback method, executed when permission is added, does the following: it hides the permission modal box, initializes the canvas video stream, starts the availability of filters, and sets the screenshot handler. If it fails to initialize, the showBrowserWarning() method is ran again.

The next set of items is just variables local to this object.

The method _initializeStream sets the _video variable to our current video element. Then, set the video’s source to the object created from the videoStream we just got from getUserMedia().

_initializeFilters sets up all the available filters by their ID and their css to execute. Then, it uses document.querySelectorAll() (see, no jquery! hah) to get all the filter buttons and add listeners to them. So if one is clicked, that CSS is executed.

Moving on, _initializeScreenshot sets up the canvas and the canvas context in that hidden screenshot section. It also adds the handler to call _launchScreenshotEditor() if the video is clicked. It continues to add other handlers, most which are pretty straight forward.

The most interesting set of handlers is the dragstart and dragover ones. dragstart is added to the decoration. This tracks the ID of the current item being dragged, the current position of it, and the current position of the mouse cursor that is doing the dragging. This is important because we need to know what offset the mouse cursor is from the actual image itself when we go to place it. See, we get the calculation of mouse cursor movement, not item movement – so we need to apply the offset of the item later on to get the exact placement of the item when done. This is all sent to the dataTransfer object of this draggable item.

Dragover just allows this to be a place where one could drop something. Finally, drop allows for the item to be dropped here. See, you can add the drop handler to all items where a draggable item can be dropped. The handler grabs the original location from the handler so it can calculate the offsets, then positions the dropped item properly.

The screenshot editor function basically sets the canvas to the size of the video, and then draws an image onto it from the video. It also grabs the style (filter) from the video and applies it to the canvas so that the look is the same. Finally it shows the modal.

_toggleScreenshotModal() is responsible for showing the modal and applying the proper styles to the body, or hiding it, resetting the position of the decorations, and hiding the screenshot modal.

The _resizeDecoration method just determines if the delta change of the mousewheel is positive or negative, and then applies that transformation to the target element. The width and height need to be changed equally, however, so the ratio is calculated ot make sure this is done properly.

Finally, on to _downloadImage(). I’ve seen a lot of people send the base64 string to a php script via iframe (ajax won’t prompt a download dialog, but iframe will)- but I didn’t want to do that. This creates a new canvas (because I don’t want to modify my current displayed one by adding decorations to it), and clones the image that was drawn to that canvas. Next, it loops through each decoration and places it as a drawn image on my new canvas. Next, the toDataURL() method is called to return the base64 image data which is passed to the byteArray. The byteArray is passed to an HTML5 Blob with the proer mime type created. Then, a URL is created from that blob that will prompt an octet-stream download. The filename is retrieved from the input box or from the placeholder, and the download link is created, and then clicked. This prompts the user for a download.

The end!

I’m sure I could do this better. Remember, the filters are not actually applied. I could make it more cross browser and more able to be on different display sizes. But overall, I wanted to prove it could be done. Remember, you can check it out at Online Photo Booth.

2 comments on “HTML5, CSS3, Javascript ONLY Photobooth with Image Download
  • Wheay says:

    Hi,

    I really like this tutorial and it is what i need, i was wondering if i could remove the filter effects, instead just add a branding border or png image as my effects, plus a timer before it captures our picture. can you also add social media sharing? thank you.

    i hope you could help me with this.

    BTW: when i download the image filter effects was removed. using chrome on mac.

    • Aaron says:

      Yeah – the filter effects are actually just CSS on top of it – I noticed that in the blog. As for the features, you are more than welcome to grab the code and customize it as much as you want. I think a timer would be easy enough too – you could just use settimeout() with javascript. Good luck!

Leave a Reply

Your email address will not be published.