В этой статье мы будем писать WEB приложение Photobooth на jQuery и PHP. Оно позволит посетителям сделать снимок с веб-камеры и загрузить его в галерею, доступную для просмостра всем остальным.
Как вы знаете, с помощью JavaScript не возможно получить доступ к веб-камере и к другим периферийным устройствам. Однако есть решение – мы можем использовать flash. Flash имеет полный доступ к веб-камере, а так же установлен на большинство компьютеров.
Решать данную задачу мы будем с помощью библиотеки webcam.js
. Это JavaScript-оболочка для flash API, что даст нам контроль над веб-камерой пользователя.
HTML
Первым шагом к построению нашего Photobooth приложения будет написание HTML структуры страницы. Поэтому создадим простой HTML файл index.html со следующим содержимым:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Пишем Photobooth на PHP, jQuery и CSS3</title>
<link rel="stylesheet" type="text/css" href="assets/fancybox/jquery.fancybox.css"/>
<link rel="stylesheet" type="text/css" href="assets/css/buttons.css"/>
<link rel="stylesheet" type="text/css" href="assets/css/styles.css"/>
</head>
<body>
<a href="http://nikitakiselev.ru/post/20" class="ref-link" title="Вернуться к статье">
Назад к статье
</a>
<div id="photos"></div>
<div id="camera">
<div id="screen"></div>
<div id="buttons">
<div class="buttonPane">
<a id="shootButton" href="" class="blueButton">Фото!</a>
</div>
<div class="buttonPane hidden">
<a id="cancelButton" href="" class="red">Отмена</a> <a id="uploadButton" href="" class="green">Загрузить!</a>
</div>
</div>
<span class="settings"></span>
<span class="camTop"></span>
<span class="tooltip"></span>
</div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/jquery-easing/1.3/jquery.easing.min.js"></script>
<script src="assets/fancybox/jquery.fancybox.pack.js"></script>
<script src="assets/jpegcam/webcam.js"></script>
<script src="assets/js/script.js"></script>
</body>
</html>
На странице у нас 3 DIV элемента:
- #topBar отображает заголовки.
- #photos куда изображения будут добавляться с помощью jQuery и метода $.getJSON.
- #camera в этом блоке находится флеш ролик webcam.swf, который мы будем использовать для доступа к камере пользователя. Так же в этом блоке находятся управляющие элементы: кнопка фотографирования и загрузки в галерею.
Управляющие элементы разделены на 2 div'a с классами .buttonPane
. С помощью jQuery мы будем переключаться между копкой фотографирования и кнопкой загрузки фото в галерею.
Перед закрывающимся тегом </body>
мы подключаем несколько javascript библиотек:
- библиотека jQuery.
- jQuery плагин fancybox для отображения фотографий во всплывающем окне.
- easing plugin добавляет новые анимации для jQuery.
- webcam.js
Если данное WEB приложение планируется использовать на высоконагруженном сайте, то есть смысл объединить все js в один и сжать.
PHP
Напишем PHP скрипт, который будет получать фото от камеры (upload.php), а так же скрипт для получения списока уже существующих изображений (browse.php)
upload.php
<?php
/*
* Этот файл получает JPEG снимок от "/assets/jpegcam/webcam.swf"
* который передаётся $_POST запросом
*/
// Скрипт работает только тогда, когда получает POST запрос
if (strtolower($_SERVER['REQUEST_METHOD']) != 'post') {
exit;
}
$folder = 'uploads/';
$filename = md5($_SERVER['REMOTE_ADDR'] . rand()) . '.jpg';
$original = $folder . $filename;
// Получаем JPEG снимок:
$input = file_get_contents('php://input');
if (md5($input) == '7d4df9cc423720b7f1f3d672b89362be') {
// Пустое изображение
exit;
}
$result = file_put_contents($original, $input);
if (!$result) {
echo '{
"error" : 1,
"message" : "Ошибка при сохранении изображения. Убедитесь, что у Вас есть доступ к папке ' . $folder . '
}';
exit;
}
$info = getimagesize($original);
if ($info['mime'] != 'image/jpeg') {
unlink($original);
exit;
}
// Переносим полученное фото в папку с полноразмерными изображениями
rename($original, 'uploads/original/' . $filename);
$original = 'uploads/original/' . $filename;
// Используем GD library для изменения размера фото,
// чтобы получить миниатюру
$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 . '"}';
Как я упоминал ранее, мы не можем общаться с веб-камерой пользователя непосредственно из JavaScript. Вот почему нам нужен flash, который имеет полный доступ к камере. Он чтобы выступать в качестве промежуточного слоя. Получить изображение от веб-камеры можно двумя путями:
- Мы можем передавать изображения напрямую из флеш к javascript (медленно и неоптимально).
- Передавать изображение напрямую в php скрипт с помощью POST.
Мы используем второй способ и передаём снимок напрямую в PHP скрипт. Так же снимок получается в формате JPEG, что означает, что мы можем сохранить его в файл напрямую из PHP, без его преобразования.
В PHP скрипте upload.php мы получаем JPEG изображение и сохраняем его в файл в папку uploads/original/. Затем генерируем миниатюру размером 154x110 px в папку uploads/thumbs. Я выбрал этот размер эскизов из соображений удобства, как он сохраняет пропорции исходного изображения (520x370 px).
browse.php
<?php
/*
* Этот скрипт сканирует папки с миниатюрами и позвращает
* их в JSON объекте. Эти миниатюры будут использоваться
* для отображения всех фото на странице, сделанных
* пользователями.
*/
// Заголовок для json:
header('Content-type: application/json');
$perPage = 24;
// Сканируем папку с миниатюрами:
$g = glob('uploads/thumbs/*.jpg');
if (!$g) {
$g = array();
}
$names = array();
$modified = array();
// Запускаем цикл от 0 до количества найденных с помощью glob() изображений,
// получаем в массив $names имена файлов, а в массив $modified время создания файла
for ($i = 0, $z = count($g); $i < $z; $i++) {
$path = explode('/', $g[$i]);
$names[$i] = array_pop($path);
$modified[$i] = filemtime($g[$i]);
}
// Функция array_multisort() сортирует массив с именами изображений $names
// в соответствии с датой создания этого изображения
array_multisort($modified, SORT_DESC, $names);
$start = 0;
// browse.php так же может разбивать результат постранично
// с помощью параметра GET, в котором хранится имя изображения,
// с которого начинать отображение галереи
if (isset($_GET['start']) && strlen($_GET['start']) > 1) {
$_GET['start'] = trim($_GET['start']);
$_GET['start'] = htmlspecialchars($_GET['start']);
$start = array_search($_GET['start'], $names);
if ($start === false) {
// Не нашёл изображение с нужным именем
$start = 0;
}
}
// В переменной nextStart будем хранить имя изображения,
// которое будет являться первым на следующей странице галереи
//
// Скрипт будет передавать это значение в $_GET['start'],
// если пользователь нажан на кнопку "Далее" в галерее.
$nextStart = '';
if ($names[$start + $perPage]) {
$nextStart = $names[$start + $perPage];
}
$names = array_slice($names, $start, $perPage);
// Возвращаем результат как JSON объект:
echo json_encode(array(
'files' => $names,
'nextStart' => $nextStart
));
Скрипт browse.php, возвращает содержимое папки с изображениями в формате JSON. С помощью функции glob()
мы сканируем папку и возвращает массив с именами файлов. Затем мы сортируем массив по дате функцией array_multisort()
, после чего "отрезаем" 24 фотографии с помощью array_slice()
.
jQuery
Плагин webcam.js предоставляет простое API, доступное как глобальный объект с именем webcam
. Он дает нам методы для получения и загрузки фотографий, а так же методы для работы с SWF-файлом.
В script.js мы будем использовать это api. Сначала мы определим некоторые переменные и наиболее часто используемые jQuery селекторы для повышения производительности:
assets/js/script.js – Часть 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>';
/*----------------------------------
Установки WEB камеры
----------------------------------*/
webcam.set_swf_url('assets/webcam/webcam.swf');
webcam.set_api_url('upload.php'); // Скрипт созранения фото
webcam.set_quality(80); // качество фото
webcam.set_shutter_sound(true, 'assets/jpegcam/shutter.mp3');
// Генерируем HTML код для области с камерой:
screen.html(
webcam.get_html(screen.width(), screen.height())
);
Переменная template в коде выше содержит HTML разметку, которая создается для каждой фотографии. Это обычная гиперссылка, у которой в качестве фона используется миниатюра загруженного изображения. Эта ссылка так же ведёт на полноразмерную версию этого изображения. Атрибут {src} мы заменим на имя файла фотографии (имена файлов генерируются автоматически в upload.php из раздела про PHP).
Далее мы будем привязывать события для кнопок управления. Обратите внимание на использование методов webcam.freeze()
и webcam.upload()
. Это дает пользователю сфотографиговать и решить, следует ли загрузить фото позднее. Webcam.Reset()
подготавливает веб-камеру для следующего фотографирования.
assets/js/script.js - Часть 2
/*----------------------------------
Привязываем события для кнопок управления
----------------------------------*/
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');
});
// Показываем/Скрываем панель с камерой:
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');
});
После этого нам нужно реализовать некоторые callbacks, предоставляемые плагином веб-камеры
assets/js/script.js- Часть 3
/*----------------------
Callbacks
----------------------*/
webcam.set_hook('onLoad',function(){
// Когда flash загружен, делаем доступными
// кнопки фотографирования и настройки камеры:
shootEnabled = true;
});
webcam.set_hook('onComplete', function(msg){
// Объект JSON, возвращаемый скриптом upload.php,
// в котором содержится имя снятой фотографии:
msg = $.parseJSON(msg);
if(msg.error) {
alert(msg.message);
}
else {
// Добавим изображение к галерее
photos.prepend(templateReplace(template,{src:msg.filename}));
initFancyBox();
}
});
webcam.set_hook('onError',function(e){
screen.html(e);
});
Здесь мы завершаем работу с WEB камерой. Далее нужно показать список последних фотографий, чтобы пользователи могли их просматривать. Мы будем делать это с помощью пользовательской функции под названием loadPics()
, которая будет общаться к browse.php:
assets/js/script.js - Часть 4
/*-------------------------------------
Получаем уже существующие фото
-------------------------------------*/
var start = '';
function loadPics(){
// Возвращает true, когда фотки загрузились:
if(this != window){
if($(this).html() == 'Загрузка..') {
// Ничего не делаем пока "Загрузка..."
// чтобы защиться от нескольких нажатий подряд
// на кнопку Далее в галерее
}
return false;
}
$(this).html('Загрузка..');
}
// AJAX запрос. В параметре "start" мы указываем
// с какой картинки отображать галерею.
// Используется для постраничной навигации
$.getJSON('browse.php',{'start':start},function(r){
photos.find('a').show();
// detach() удаляет элемент #loadMore
// при использовании detach, jQuery
// не удаляет информацию о элементе и поэтому
// он может быть восстановлен
var loadMore = $('#loadMore').detach();
if(!loadMore.length){
loadMore = $('<span>',{
id : 'loadMore',
html : 'Далее',
click : loadPics
});
}
$.each(r.files,function(i,filename){
photos.append(templateReplace(template,{src:filename}));
});
// Если есть следующас страница с фотками:
if(r.nextStart){
start = r.nextStart;
photos.find('a:last').hide();
photos.append(loadMore.html('Далее'));
}
// Переинициализируем Fancy Box каждый раз,
// когда изменяются фотки на странице:
initFancyBox();
});
return false;
}
// Вызывает loadPics() при загрузке страницы
loadPics();
Так как loadPics()
привязана еще и к событию click
для кнопки загрузки дополнительнх фотографий "Далее", эта функция может быть вызвана двумя способами: нормальный и качестве обратного вызова. Разница заключается в том, что объект this будет window или DOM-элементом. Мы проверяем это и создаём защиту от многократных кликов подряд, чтобы небыло множественных запросов к browse.php.
И наконец мы напишем вспомогательные функции, используемые в остальной части кода.
assets/js/script.js - Часть 5
/*----------------------
Вспомогательные функции
------------------------*/
// Инициализируем fancybox скрипт
function initFancyBox(filename) {
photos.find('a:visible').fancybox({
'transitionIn': 'elastic',
'transitionOut': 'elastic',
'overlayColor': '#111'
});
}
// Функция переключает режимы отображения панелей .buttonPane
function togglePane() {
var visible = $('#camera .buttonPane:visible:first');
var hidden = $('#camera .buttonPane:hidden:first');
visible.fadeOut('fast', function () {
hidden.show();
});
}
// Функция-парсер, заменяет шаблонные значения "{KEYWORD}"
// их реальными значениями
function templateReplace(template, data) {
return template.replace(/{([^}]+)}/g, function (match, group) {
return data[group.toLowerCase()];
});
}
});
Теперь, когда мы разобрали весь код, давайте напишем CSS-стили.
CSS
С внедрением поддержки CSS3 в Firefox 4 эффекты переходов можно, наконец то, использовать на всю катушку.
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;
}
В фрагменте выше вы можете увидеть, что мы указали 0.25-ти секундный переход на плитки с фотографиями и к кнопкой "загрузить еще". Теперь при наведении мышью на эти элементы они будут анимироваться.
Заключение
Вы можете использовать это приложение как дополнение к форуму сообщества или другой социальной сети на сайте. При необходимости можно построить встроить комментарии к фотографиями и более глубоко интегрировать с Вашим сайтом.