Fotomatón con PHP, jQuery y CSS3

En este tutorial, construiremos un fotomatón con jQuery y PHP. Permitirá a los visitantes de su sitio web tomar una instantánea con su cámara web y cargarla desde una elegante interfaz CSS3.

Como sabrá, no es posible acceder a cámaras web y otros dispositivos periféricos directamente desde JavaScript (y no lo será por algún tiempo). Sin embargo, hay una solución a nuestro problema:podemos usar una película flash. Flash tiene soporte perfecto para cámaras web y está instalado en casi todas las computadoras habilitadas para Internet.

La solución que vamos a utilizar para esta aplicación es webcam.js. Es un contenedor de JavaScript alrededor de la API de Flash que nos da control sobre la cámara web del usuario.

HTML

El primer paso para construir nuestro Photobooth es establecer la estructura HTML de la página principal. Usaremos jQuery para obtener una lista de las últimas fotos, por lo que no necesitamos incrustar ninguna lógica PHP aquí. Esto significa que podemos dejarlo como un archivo HTML sin formato.

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>

Hay tres divisiones principales en la página:

  • #barra superior muestra los encabezados;
  • #fotos es donde se insertan las imágenes después de que se solicitan con el método $.getJSON de jQuery;
  • #cámara contiene la película webcam.swf (que estamos usando para comunicarnos con la cámara web). También contiene los botones de control para tomar fotos y cargarlas.

Los botones de control se dividen en dos .buttonPane divs. En la parte de jQuery del tutorial, crearemos una función simple para alternar entre los paneles.

En la parte inferior del cuerpo, incluimos varios archivos JavaScript. Comenzando con la biblioteca jQuery, también estamos agregando el complemento fancybox para mostrar las fotos, el complemento de aceleración (para hacer que fancybox sea aún más elegante), webcam.js, el complemento que nos permite comunicarnos con cámaras web a través de flash, y finalmente el nuestro. secuencia de comandos.js para hacer que todo esto funcione en conjunto.

Tenga en cuenta que si está ajustando su sitio web para una alta carga, es posible que desee combinar todos estos archivos JS. Esto hará que la página se cargue más rápido ya que los archivos JavaScript bloquean la página mientras se carga.

PHP

Aunque la página principal es HTML simple y antiguo, necesitamos PHP para que nuestro fotomatón funcione. Para ser más exactos, hay dos funciones de la aplicación para las que necesitamos PHP:recibir la imagen cargada desde flash y para enumerar los archivos cargados.

subir.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.'"}';

Como mencioné anteriormente, no podemos comunicarnos con cámaras web directamente desde JavaScript. Es por eso que necesitamos flash, que tiene un excelente soporte de cámara web, para actuar como una capa intermedia. Esto nos deja con dos opciones:

  • podemos hacer que flash exporte la instantánea y que esté disponible para JavaScript (lento e ineficaz);
  • haga que Flash suba la foto directamente a un script PHP.

Con sensatez, el complemento de la cámara web flash utiliza el segundo enfoque. También tiene la ventaja de cargar la instantánea como una imagen JPEG válida, lo que significa que podemos guardarla en un archivo directamente con PHP, sin tener que convertirla.

En nuestro upload.php validamos que los datos cargados sean una imagen JPEG, guárdelos en un archivo en uploads/original/ y genere una miniatura de 154 x 110 px. Elegí este tamaño de miniatura por conveniencia, ya que comparte la misma proporción de ancho y alto que la imagen original (520 por 370 px), lo que facilita el cambio de tamaño.

navegar.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
));

El navegar.php El archivo enumera el contenido de las carpetas de imágenes como un objeto JSON. Lo hace con la función glob de PHP, que escanea la carpeta y devuelve una matriz con los nombres de los archivos. Luego, ordenamos esta matriz de acuerdo con las fechas de carga de las fotos con la función array_multisort, después de lo cual la cortamos con array_slice para devolver solo 24 fotos a la vez.

jQuery

Como mencioné anteriormente, estamos usando el complemento webcam.js para controlar la cámara web del usuario. Este complemento expone una API simple, disponible como un objeto global llamado webcam . Nos brinda métodos para tomar y cargar fotos, y para generar el código de inserción necesario para el archivo swf.

En script.js a continuación, usaremos esta API y crearemos nuestra secuencia de comandos de fotomatón a su alrededor. Primero definiremos algunas variables y almacenaremos en caché los selectores de jQuery más utilizados en todo el código para un mejor rendimiento:

activos/js/script.js - Parte 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())
    );

El template La variable anterior contiene el marcado que se generará para cada foto. Es básicamente un hipervínculo que tiene la miniatura de la foto como imagen de fondo y que apunta a la toma de tamaño completo. El {src} el atributo se reemplaza con el nombre de archivo real de la foto (los nombres de archivo fueron generados automáticamente por upload.php en la sección anterior).

A continuación vincularemos detectores de eventos para los botones de control. Observe el uso de webcam.freeze() y webcam.upload() métodos. Esto le da al usuario la capacidad de tomar una foto y decidir si la carga más tarde. cámara web.reset() prepara la cámara web para otra toma.

activos/js/script.js - Parte 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');
    });

Después de esto, necesitaremos implementar algunas de las devoluciones de llamada expuestas por el complemento de la cámara web:

activos/js/script.js - Parte 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);
    });

Esto completa la integración de la cámara web. Sin embargo, aún necesitamos mostrar una lista con las fotos más recientes (y brindarles a los usuarios una forma de navegar a través de imágenes más antiguas también). Haremos esto con una función personalizada llamada loadPics() que se comunicará con browse.php :

activos/js/script.js - Parte 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();

Como loadPics() está vinculado como un controlador para el clic evento de Cargar más mosaico, esta función se puede llamar de dos maneras:la normal y como devolución de llamada. La diferencia es que el this el objeto de la función apunta a window , o al elemento DOM. Podemos verificar esto y tomar las medidas apropiadas, como evitar que los clics dobles emitan múltiples solicitudes para navegar.php.

Por último, tenemos las funciones auxiliares, que se utilizan en el resto del código.

activos/js/script.js - Parte 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()];
        });
    }
});

Ahora que hemos discutido todo el código, digamos algunas palabras sobre los estilos CSS.

CSS3

Con la reciente introducción de Firefox 4, las transiciones CSS3 finalmente pueden convertirse en un miembro completamente calificado de nuestra caja de herramientas para desarrolladores. En nuestro fotomatón, estamos usando CSS3 para agregar un poco de clase al fotomatón.

activos/css/estilos.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;
}

En el fragmento de arriba, puedes ver que hemos definido una transición de 0,25 s en los anclajes de las fotos (estos contienen nuestras imágenes). Esto animará cada cambio en los estilos de estos elementos, incluidos los aplicados por :hover definiciones De hecho, esto hace que todas las fotos se desvanezcan al 50 % cuando pasamos el cursor sobre #fotos. div, excepto el que está directamente debajo del puntero del mouse.

Con esa misma definición de transición, también afectamos el lapso #loadMore. Tiene una regla de sombra de texto aplicada al pasar el mouse, que se anima en un efecto de brillo suave.

¡Con esto nuestro fotomatón está completo!

Conclusión

Puede usar esta aplicación como una adición divertida a un foro comunitario u otro sitio web de redes sociales. Opcionalmente, puede crear un campo de comentario para acompañar la foto o integrarlo más profundamente con su sitio.

¿Te gustó el tutorial de esta semana? Comparta sus pensamientos en la sección de comentarios.