Photobooth with PHP, jQuery and CSS3

Download

In this tutorial, we will be building a jQuery and PHP powered photobooth. It will allow your website visitors to take a snapshot with their web camera and upload it from a neat CSS3 interface.

As you might know, it is not possible to access web cameras and other peripheral devices directly from JavaScript (and it won't be for some time). However there is a solution to our problem - we can use a flash movie. Flash has perfect web camera support, and is installed on nearly all internet - enabled computers.

The solution we are going to use for this app is webcam.js. It is a JavaScript wrapper around flash's API that gives us control over the user's webcam.

HTML

The first step to building our Photobooth is laying down the HTML structure of the main page. We will be using jQuery to fetch a list of the latest photos, so we don't need to embed any PHP logic here. This means we can leave it as a plain HTML file.

index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Photobooth with PHP, jQuery and CSS3</title>

<link rel="stylesheet" type="text/css" href="assets/css/styles.css" />
<link rel="stylesheet" type="text/css" href="assets/fancybox/jquery.fancybox-1.3.4.css" />

</head>
<body>

<div id="topBar">
    <h1>jQuery &amp; CSS3 Photobooth</h1>
    <h2>&laquo; Go back to Tutorialzine</h2>
</div>

<div id="photos"></div>

<div id="camera">
    <span class="tooltip"></span>
    <span class="camTop"></span>

    <div id="screen"></div>
    <div id="buttons">
        <div class="buttonPane">
            <a id="shootButton" href="" class="blueButton">Shoot!</a>
        </div>
        <div class="buttonPane hidden">
            <a id="cancelButton" href="" class="blueButton">Cancel</a> <a id="uploadButton" href="" class="greenButton">Upload!</a>
        </div>
    </div>

    <span class="settings"></span>
</div>

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
<script src="assets/fancybox/jquery.easing-1.3.pack.js"></script>
<script src="assets/fancybox/jquery.fancybox-1.3.4.pack.js"></script>
<script src="assets/webcam/webcam.js"></script>
<script src="assets/js/script.js"></script>

</body>
</html>

There are three main divs in the page:

  • #topBar displays the headings;
  • #photos is where the images are inserted after they are requested with jQuery's $.getJSON method;
  • #camera holds the webcam.swf movie (which we are using to communicate with the web camera). It also holds the control buttons for taking photos and uploading.

The control buttons are divided in two .buttonPane divs. In the jQuery part of the tutorial, we will be making a simple function to toggle between the panes.

At the bottom of the body, we are including a number of JavaScript files. Starting with the jQuery library, we are also adding the fancybox plugin for displaying the photos, the easing plugin (to make fancybox even fancier), webcam.js - the plugin that enables us to communicate with web cameras through flash, and finally our own script.js to make all this work together.

Note that if you are tweaking your website for high load, you might want to combine all these JS files together. This will make the page load faster as JavaScript files block the page while loading.

jquery-php-css3-photobooth.jpg

PHP

Although the main page is plain old HTML, we do need PHP to make our photo booth work. To be more exact, there are two features of the app that we need PHP for - receiving the uploaded image from flash, and for listing the uploaded files.

upload.php

/*
    This file receives the JPEG snapshot from
    assets/webcam/webcam.swf as a POST request.
*/

// We only need to handle POST requests:
if(strtolower($_SERVER['REQUEST_METHOD']) != 'post'){
    exit;
}

$folder = 'uploads/';
$filename = md5($_SERVER['REMOTE_ADDR'].rand()).'.jpg';

$original = $folder.$filename;

// The JPEG snapshot is sent as raw input:
$input = file_get_contents('php://input');

if(md5($input) == '7d4df9cc423720b7f1f3d672b89362be'){
    // Blank image. We don't need this one.
    exit;
}

$result = file_put_contents($original, $input);
if (!$result) {
    echo '{
        "error"     : 1,
        "message"   : "Failed save the image. Make sure you chmod the uploads folder and its subfolders to 777."
    }';
    exit;
}

$info = getimagesize($original);
if($info['mime'] != 'image/jpeg'){
    unlink($original);
    exit;
}

// Moving the temporary file to the originals folder:
rename($original,'uploads/original/'.$filename);
$original = 'uploads/original/'.$filename;

// Using the GD library to resize
// the image into a thumbnail:

$origImage  = imagecreatefromjpeg($original);
$newImage   = imagecreatetruecolor(154,110);
imagecopyresampled($newImage,$origImage,0,0,0,0,154,110,520,370); 

imagejpeg($newImage,'uploads/thumbs/'.$filename);

echo '{"status":1,"message":"Success!","filename":"'.$filename.'"}';

As I mentioned earlier, we can't communicate with web cameras directly from JavaScript. This is why we need flash, which has excellent web camera support, to act as an intermediate layer. This leaves us with two choices:

  • we can have flash export the snapshot and make it available to JavaScript (slow and ineffective);
  • have flash upload the photo directly to a PHP script.

Sensibly, the flash webcam plugin uses the second approach. It also has the benefit of uploading the snapshot as a valid JPEG image, which means we can save it to a file directly with PHP, without having to convert it.

In our upload.php we validate that the uploaded data is a JPEG image, save it to a file in the uploads/original/ directory, and generate a 154 -by- 110 px thumbnail. I chose this thumbnail size out of convenience, as it shares the same width to height ratio as the original image (520 -by- 370 px), which makes the resize easier.

webcam-shot-flash-jquery-php-css3.jpg

browse.php

/*
    In this file we are scanning the image folders and
    returning a JSON object with file names. It is used
    by jQuery to display the images on the main page:
*/

// The standard header for json data:
header('Content-type: application/json');

$perPage = 24;

// Scanning the thumbnail folder for JPG images:
$g = glob('uploads/thumbs/*.jpg');

if(!$g){
    $g = array();
}

$names = array();
$modified = array();

// We loop though the file names returned by glob,
// and we populate a second file with modifed timestamps.

for($i=0,$z=count($g);$i<$z;$i++){
    $path = explode('/',$g[$i]);
    $names[$i] = array_pop($path);

    $modified[$i] = filemtime($g[$i]);
}

// Multisort will sort the array with the filenames
// according to their timestamps, given in $modified:

array_multisort($modified,SORT_DESC,$names);

$start = 0;

// browse.php can also paginate results with an optional
// GET parameter with the filename of the image to start from:

if(isset($_GET['start']) && strlen($_GET['start'])>1){
    $start = array_search($_GET['start'],$names);

    if($start === false){
        // Such a picture was not found
        $start = 0;
    }
}

// nextStart is returned alongside the filenames,
// so the script can pass it as a $_GET['start']
// parameter to this script if "Load More" is clicked

$nextStart = '';

if($names[$start+$perPage]){
    $nextStart = $names[$start+$perPage];
}

$names = array_slice($names,$start,$perPage);

// Formatting and returning the JSON object:

echo json_encode(array(
    'files' => $names,
    'nextStart' => $nextStart
));

The browse.php file lists the contents of the image folders as a JSON object. It does it with PHP's glob function, which scans the folder and returns an array with file names. We then sort this array according to the photo upload dates with the array_multisort function, after which we slice it with array_slice to return only 24 photos at a time.

jQuery

As I mentioned earlier, we are using the webcam.js plugin to control the user's web camera. This plugin exposes a simple API, available as a global object named webcam. It gives us methods for taking and uploading photos, and for generating the necessary embed code for the swf file.

In script.js below, we will be using this api and build our photo booth script around it. First we will define some variables and cache the most commonly used jQuery selectors throughout the code for better performance:

assets/js/script.js - Part 1

$(document).ready(function(){

    var camera = $('#camera'),
        photos = $('#photos'),
        screen =  $('#screen');

    var template = '<a href="uploads/original/{src}" rel="cam" '
        +'style="background-image:url(uploads/thumbs/{src})"></a>';

    /*----------------------------------
        Setting up the web camera
    ----------------------------------*/

    webcam.set_swf_url('assets/webcam/webcam.swf');
    webcam.set_api_url('upload.php');   // The upload script
    webcam.set_quality(80);             // JPEG Photo Quality
    webcam.set_shutter_sound(true, 'assets/webcam/shutter.mp3');

    // Generating the embed code and adding it to the page:
    screen.html(
        webcam.get_html(screen.width(), screen.height())
    );

The template variable above holds the markup that will be generated for each photo. It is basically a hyperlink that has the thumbnail of the photo as its background-image, and which points to the full size shot. The {src} attribute gets replaced with the actual file name of the photo (the file names were generated automatically by upload.php in the previous section).

Next we will be binding event listeners for the control buttons. Notice the use of webcam.freeze(), and the webcam.upload() methods. This gives the user the ability to take a shot and decide whether to upload it later. webcam.reset() prepares the web camera for another shot.

assets/js/script.js - Part 2

  /*----------------------------------
        Binding event listeners
    ----------------------------------*/

    var shootEnabled = false;

    $('#shootButton').click(function(){

        if(!shootEnabled){
            return false;
        }

        webcam.freeze();
        togglePane();
        return false;
    });

    $('#cancelButton').click(function(){
        webcam.reset();
        togglePane();
        return false;
    });

    $('#uploadButton').click(function(){
        webcam.upload();
        webcam.reset();
        togglePane();
        return false;
    });

    camera.find('.settings').click(function(){
        if(!shootEnabled){
            return false;
        }

        webcam.configure('camera');
    });

    // Showing and hiding the camera panel:

    var shown = false;
    $('.camTop').click(function(){

        $('.tooltip').fadeOut('fast');

        if(shown){
            camera.animate({
                bottom:-466
            });
        }
        else {
            camera.animate({
                bottom:-5
            },{easing:'easeOutExpo',duration:'slow'});
        }

        shown = !shown;
    });

    $('.tooltip').mouseenter(function(){
        $(this).fadeOut('fast');
    });

After this we will need to implement some of the callbacks exposed by the webcam plugin:

assets/js/script.js - Part 3

  /*----------------------
        Callbacks
    ----------------------*/

    webcam.set_hook('onLoad',function(){
        // When the flash loads, enable
        // the Shoot and settings buttons:
        shootEnabled = true;
    });

    webcam.set_hook('onComplete', function(msg){

        // This response is returned by upload.php
        // and it holds the name of the image in a
        // JSON object format:

        msg = $.parseJSON(msg);

        if(msg.error){
            alert(msg.message);
        }
        else {
            // Adding it to the page;
            photos.prepend(templateReplace(template,{src:msg.filename}));
            initFancyBox();
        }
    });

    webcam.set_hook('onError',function(e){
        screen.html(e);
    });

This completes the web camera integration. However we still need to display a list with the latest photos (and give users a way to browse through older images as well). We will be doing this with a custom function called loadPics() which will communicate with browse.php:

assets/js/script.js - Part 4

  /*-------------------------------------
        Populating the page with images
    -------------------------------------*/

    var start = '';

    function loadPics(){

        // This is true when loadPics is called
        // as an event handler for the LoadMore button:

        if(this != window){
            if($(this).html() == 'Loading..'){
                // Preventing more than one click
                return false;
            }
            $(this).html('Loading..');
        }

        // Issuing an AJAX request. The start parameter
        // is either empty or holds the name of the first
        // image to be displayed. Useful for pagination:

        $.getJSON('browse.php',{'start':start},function(r){

            photos.find('a').show();
            var loadMore = $('#loadMore').detach();

            if(!loadMore.length){
                loadMore = $('<span>',{
                    id          : 'loadMore',
                    html        : 'Load More',
                    click       : loadPics
                });
            }

            $.each(r.files,function(i,filename){
                photos.append(templateReplace(template,{src:filename}));
            });

            // If there is a next page with images:
            if(r.nextStart){

                // r.nextStart holds the name of the image
                // that comes after the last one shown currently.

                start = r.nextStart;
                photos.find('a:last').hide();
                photos.append(loadMore.html('Load More'));
            }

            // We have to re-initialize fancybox every
            // time we add new photos to the page:

            initFancyBox();
        });

        return false;
    }

    // Automatically calling loadPics to
    // populate the page onload:

    loadPics();

As loadPics() is bound as a handler for the click event of the Load More tile, this function can be called in two ways: the normal one and as a callback. The difference is that the this object of the function points either to window, or to the DOM element. We can check for this and take appropriate action, like preventing double clicks from issuing multiple requests to browse.php.

Lastly, we have the helper functions, used throughout the rest of the code.

assets/js/script.js - Part 5

  /*----------------------
        Helper functions
    ------------------------*/

    // This function initializes the
    // fancybox lightbox script.

    function initFancyBox(filename){
        photos.find('a:visible').fancybox({
            'transitionIn'  : 'elastic',
            'transitionOut' : 'elastic',
            'overlayColor'  : '#111'
        });
    }

    // This function toggles the two
    // .buttonPane divs into visibility:

    function togglePane(){
        var visible = $('#camera .buttonPane:visible:first');
        var hidden = $('#camera .buttonPane:hidden:first');

        visible.fadeOut('fast',function(){
            hidden.show();
        });
    }

    // Helper function for replacing "{KEYWORD}" with
    // the respectful values of an object:

    function templateReplace(template,data){
        return template.replace(/{([^}]+)}/g,function(match,group){
            return data[group.toLowerCase()];
        });
    }
});

Now that we've discussed all of the code, lets say a few words about the CSS styles.

CSS3

With the recent introduction of Firefox 4, CSS3 transitions can finally become a fully qualified member of our developer toolbox. In our photobooth, we are using CSS3 to add a bit of class to the photo booth.

assets/css/styles.css

/*-------------------
    Photo area
--------------------*/

#photos{
    margin: 60px auto 100px;
    overflow: hidden;
    width: 880px;
}

#photos:hover a{
    opacity:0.5;
}

#photos a,
#loadMore{
    background-position: center center;
    background-color: rgba(14, 14, 14, 0.3);
    float: left;
    height: 110px;
    margin: 1px 1px 0 0;
    overflow: hidden;
    width: 145px;

    -moz-transition:0.25s;
    -webkit-transition:0.25s;
    -o-transition:0.25s;
    transition:0.25s;
}

#photos a:hover{
    opacity:1;
}

#loadMore{
    cursor: pointer;
    line-height: 110px;
    text-align: center;
    text-transform: uppercase;
    font-size:10px;
}

#loadMore:hover{
    color:#fff;
    text-shadow:0 0 4px #fff;
}

In the fragment above, you can see that we've defined a 0.25s transition on the photo anchors (these hold our images). This will animate every change to the styles of these elements, including those applied by :hover definitions. This in effect makes all the photos fade out to 50% when we hover over the #photos div, except the one directly beneath the mouse pointer.

With that same transition definition, we also affect the #loadMore span. It has a text shadow rule applied on hover, which gets animated into a smooth glow effect.

jquery-photobooth-hover.jpg

With this our photo booth is complete!

Conclusion

You can use this app as a fun addition to a community forum or other social networking website. You can optionally build in a comment field to go with the photo, or integrate it more deeply with your site.

Did you like this week's tutorial? Share you thoughts in the comment section.

Bootstrap Studio

The revolutionary web design tool for creating responsive websites and apps.

Learn more

Related Articles