Lecaw

Учимся создавать Web-сайты

Фотогалерея с комментариями как в Facebook

June 6, 2012 302hits

Вы когда-нибудь думали о собственной фотогалерее в стиле Facebook с системой комментариев? Я думаю – да. Сегодня я покажу вам как воплотить эту идею в реальность. Основная идея – когда мы щелкаем по изображениям – они открываются в окне (ajax), в котором слева находиться большое изображение, а справа раздел с комментариями. Все изображения находятся в базе данных (MYSQL). И, конечно,  что бы достигнуть нашего результата мы будем использовать PHP. Кроме того, наша система комментариев предотвратит добавление больше 1 комментария в 10 минут (чтобы избежать спама).

 ДЕМОИсходные файлы

 

 

 

Шаг 1. SQL

Для нашей галереи я подготовил две таблицы SQL: первая таблица ведет учет наших изображений. Она содержит несколько полей: заголовок, имя файла, описание, время добавления и количество комментариев. Другая таблица сохраняет комментарии:

CREATE TABLE IF NOT EXISTS `s281_photos` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `title` varchar(255) default '',
  `filename` varchar(255) default '',
  `description` text NOT NULL,
  `when` int(11) NOT NULL default '0',
  `comments_count` int(11) NOT NULL default '0',
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
INSERT INTO `s281_photos` (`title`, `filename`, `description`, `when`) VALUES
('Item #1', 'photo1.jpg', 'Description of Item #1', UNIX_TIMESTAMP()),
('Item #2', 'photo2.jpg', 'Description of Item #2', UNIX_TIMESTAMP()+1),
('Item #3', 'photo3.jpg', 'Description of Item #3', UNIX_TIMESTAMP()+2),
('Item #4', 'photo4.jpg', 'Description of Item #4', UNIX_TIMESTAMP()+3),
('Item #5', 'photo5.jpg', 'Description of Item #5', UNIX_TIMESTAMP()+4),
('Item #6', 'photo6.jpg', 'Description of Item #6', UNIX_TIMESTAMP()+5),
('Item #7', 'photo7.jpg', 'Description of Item #7', UNIX_TIMESTAMP()+6),
('Item #8', 'photo8.jpg', 'Description of Item #8', UNIX_TIMESTAMP()+7),
('Item #9', 'photo9.jpg', 'Description of Item #9', UNIX_TIMESTAMP()+8),
('Item #10', 'photo10.jpg', 'Description of Item #10', UNIX_TIMESTAMP()+9);
CREATE TABLE IF NOT EXISTS `s281_items_cmts` (
  `c_id` int(11) NOT NULL AUTO_INCREMENT ,
  `c_item_id` int(12) NOT NULL default '0',
  `c_ip` varchar(20) default NULL,
  `c_name` varchar(64) default '',
  `c_text` text NOT NULL ,
  `c_when` int(11) NOT NULL default '0',
  PRIMARY KEY (`c_id`),
  KEY `c_item_id` (`c_item_id`)
) ENGINE=MYISAM DEFAULT CHARSET=utf8;

Шаг 2. PHP

Теперь, создайте пустой файл index.php и поместите в него следующий код:

index.php

<?php
// disable warnings
if (version_compare(phpversion(), "5.3.0", ">=") == 1)
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
else
error_reporting(E_ALL & ~E_NOTICE); require_once('classes/CMySQL.php'); // include service classes to work with database and comments
require_once('classes/CMyComments.php'); if ($_POST['action'] == 'accept_comment') {
echo $GLOBALS['MyComments']->acceptComment();
exit;
} // prepare a list with photos
$sPhotos = '';
$aItems = $GLOBALS['MySQL']->getAll("SELECT * FROM `s281_photos` ORDER by `when` ASC"); // get photos info
foreach ($aItems as $i => $aItemInfo) {
$sPhotos .= '<div class="photo"><img src="/images/thumb_'.$aItemInfo['filename'].'" id="'.$aItemInfo['id'].'" /><p>'.$aItemInfo['title'].' item</p><i>'.$aItemInfo['description'].'</i></div>';
} ?>
<!DOCTYPE html>
<html lang="en"><head>
<meta charset="utf-8" />
<title>Facebook like photo gallery with comments | Script Tutorials</title> <!-- Link styles -->
<link href="/css/main.css" rel="stylesheet" type="text/css" /> <!-- Link scripts -->
<script src="https://www.google.com/jsapi"></script>
<script>
google.load("jquery", "1.7.1");
</script>
<script src="/js/script.js"></script>
</head>
<body>
<header>
<h2>Facebook like photo gallery with comments</h2>
<a href="#" class="stuts">Back to original tutorial on <span>Script Tutorials</span></a>
</header> <!-- Container with last photos -->
<div class="container">
<h1>Last photos:</h1>
<?= $sPhotos ?>
</div> <!-- Hidden preview block -->
<div id="photo_preview" style="display:none">
<div class="photo_wrp">
<img class="close" src="/images/close.gif" />
<div style="clear:both"></div>
<div class="pleft">test1</div>
<div class="pright">test2</div>
<div style="clear:both"></div>
</div>
</div>
</body></html>

Мы только что создали основной индексный файл нашей галереи. По умолчанию – сценарий генерирует список изображений (с заголовком и описанием), а  также генерирует пустой скрытый объект, который мы подготовим к использованию, чтобы принять пользовательский контент с ajax запросами. Кроме того, когда мы добавляем комментарии, мы передаем запрос (чтобы принять новый комментарий) в класс комментариев.

photos_ajx.php

<?php
// disable warnings
if (version_compare(phpversion(), "5.3.0", ">=") == 1)
error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED);
else
error_reporting(E_ALL & ~E_NOTICE); if ($_POST['action'] == 'get_info' && (int)$_POST['id'] > 0) { require_once('classes/CMySQL.php'); // include service classes to work with database and comments
require_once('classes/CMyComments.php'); // get photo info
$iPid = (int)$_POST['id'];
$aImageInfo = $GLOBALS['MySQL']->getRow("SELECT * FROM `s281_photos` WHERE `id` = '{$iPid}'"); // prepare last 10 comments
$sCommentsBlock = $GLOBALS['MyComments']->getComments($iPid); $aItems = $GLOBALS['MySQL']->getAll("SELECT * FROM `s281_photos` ORDER by `when` ASC"); // get photos info // Prev & Next navigation
$sNext = $sPrev = '';
$iPrev = (int)$GLOBALS['MySQL']->getOne("SELECT `id` FROM `s281_photos` WHERE `id` < '{$iPid}' ORDER BY `id` DESC LIMIT 1");
$iNext = (int)$GLOBALS['MySQL']->getOne("SELECT `id` FROM `s281_photos` WHERE `id` > '{$iPid}' ORDER BY `id` ASC LIMIT 1");
$sPrevBtn = ($iPrev) ? '<div class="preview_prev"><img src="/images/prev.png" alt="prev" /></div>' : '';
$sNextBtn = ($iNext) ? '<div class="preview_next"><img src="/images/next.png" alt="next" /></div>' : ''; require_once('classes/Services_JSON.php');
$oJson = new Services_JSON();
header('Content-Type:text/javascript');
echo $oJson->encode(array(
'data1' => '<img class="fileUnitSpacer" src="/images/'. $aImageInfo['filename'] .'">' . $sPrevBtn . $sNextBtn,
'data2' => $sCommentsBlock,
));
exit;
}

Этот файл передает информацию о требуемой фотографии. Это - увеличенное изображение, блок с комментариями и кнопками навигации. Мы используем класс комментариев:

classes/CMyComments.php

<?php
class CMyComments {
 // constructor
function CMyComments() {
} // return comments block
function getComments($i) {
// draw last 10 comments
$sComments = '';
$aComments = $GLOBALS['MySQL']->getAll("SELECT * FROM `s281_items_cmts` WHERE `c_item_id` = '{$i}' ORDER BY `c_when` DESC LIMIT 10"); foreach ($aComments as $i => $aCmtsInfo) {
$sWhen = date('F j, Y H:i', $aCmtsInfo['c_when']);
$sComments .= <<<EOF
<div class="comment" id="{$aCmtsInfo['c_id']}">
<p>Comment from {$aCmtsInfo['c_name']} <span>({$sWhen})</span>:</p>
<p>{$aCmtsInfo['c_text']}</p>
</div>
EOF;
} return <<<EOF
<div class="comments" id="comments">
<h2>Comments</h2>
<div id="comments_warning1" style="display:none">Don`t forget to fill both fields (Name and Comment)</div>
<div id="comments_warning2" style="display:none">You can't post more than one comment per 10 minutes (spam protection)</div>
<form>
<table>
<tr><td class="label"><label>Your name: </label></td><td class="field"><input type="text" value="" title="Please enter your name" id="name" /></td></tr>
<tr><td class="label"><label>Comment: </label></td><td class="field"><textarea name="text" id="text"></textarea></td></tr>
<tr><td class="label"> </td><td class="field"><button>Post comment</button></td></tr>
</table>
</form>
<div id="comments_list">{$sComments}</div>
</div>
EOF;
} function acceptComment() {
$iItemId = (int)$_POST['id']; // prepare necessary information
$sIp = $this->getVisitorIP();
$sName = $GLOBALS['MySQL']->escape(strip_tags($_POST['name']));
$sText = $GLOBALS['MySQL']->escape(strip_tags($_POST['text'])); if ($sName && $sText) {
// check - if there is any recent post from you or not
$iOldId = $GLOBALS['MySQL']->getOne("SELECT `c_item_id` FROM `s281_items_cmts` WHERE `c_item_id` = '{$iItemId}' AND `c_ip` = '{$sIp}' AND `c_when` >= UNIX_TIMESTAMP() - 600 LIMIT 1");
if (! $iOldId) {
// if everything is fine - allow to add comment
$GLOBALS['MySQL']->res("INSERT INTO `s281_items_cmts` SET `c_item_id` = '{$iItemId}', `c_ip` = '{$sIp}', `c_when` = UNIX_TIMESTAMP(), `c_name` = '{$sName}', `c_text` = '{$sText}'");
$GLOBALS['MySQL']->res("UPDATE `s281_photos` SET `comments_count` = `comments_count` + 1 WHERE `id` = '{$iItemId}'"); // and print out last 10 comments
$sOut = '';
$aComments = $GLOBALS['MySQL']->getAll("SELECT * FROM `s281_items_cmts` WHERE `c_item_id` = '{$iItemId}' ORDER BY `c_when` DESC LIMIT 10");
foreach ($aComments as $i => $aCmtsInfo) {
$sWhen = date('F j, Y H:i', $aCmtsInfo['c_when']);
$sOut .= <<<EOF
<div class="comment" id="{$aCmtsInfo['c_id']}">
<p>Comment from {$aCmtsInfo['c_name']} <span>({$sWhen})</span>:</p>
<p>{$aCmtsInfo['c_text']}</p>
</div>
EOF;
}
return $sOut;
}
}
return 1;
} // get visitor IP
function getVisitorIP() {
$ip = "0.0.0.0";
if( ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) && ( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) ) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
} elseif( ( isset( $_SERVER['HTTP_CLIENT_IP'])) && (!empty($_SERVER['HTTP_CLIENT_IP'] ) ) ) {
$ip = explode(".",$_SERVER['HTTP_CLIENT_IP']);
$ip = $ip[3].".".$ip[2].".".$ip[1].".".$ip[0];
} elseif((!isset( $_SERVER['HTTP_X_FORWARDED_FOR'])) || (empty($_SERVER['HTTP_X_FORWARDED_FOR']))) {
if ((!isset( $_SERVER['HTTP_CLIENT_IP'])) && (empty($_SERVER['HTTP_CLIENT_IP']))) {
$ip = $_SERVER['REMOTE_ADDR'];
}
}
return $ip;
}
} $GLOBALS['MyComments'] = new CMyComments(); ?>

Этот класс выполняет две основных функции – он может принять новые комментарии, и также он может дать нам поле с комментариями. Есть еще два класса: CMySQL.php и Services_JSON.php. Они - два известных класса, которые работают с базой данных и json. Можете скорректировать настройки базы данных в классе базы данных. Оба класса, доступные в нашем пакете.

Шаг 3. JavaScript

Теперь мы должны подготовить анимацию пользовательского интерфейса с использованием javascript:

js/script.js

// close photo preview block
function closePhotoPreview() {
    $('#photo_preview').hide();
    $('#photo_preview .pleft').html('empty');
    $('#photo_preview .pright').html('empty');
};
// display photo preview block
function getPhotoPreviewAjx(id) {
    $.post('photos_ajx.php', {action: 'get_info', id: id},
        function(data){
            $('#photo_preview .pleft').html(data.data1);
            $('#photo_preview .pright').html(data.data2);
            $('#photo_preview').show();
        }, "json"
    );
};
// submit comment
function submitComment(id) {
    var sName = $('#name').val();
    var sText = $('#text').val();
    if (sName && sText) {
        $.post('index.php', { action: 'accept_comment', name: sName, text: sText, id: id },
            function(data){
                if (data != '1') {
                    $('#comments_list').fadeOut(1000, function () {
                        $(this).html(data);
                        $(this).fadeIn(1000);
                    });
                } else {
                    $('#comments_warning2').fadeIn(1000, function () {
                        $(this).fadeOut(1000);
                    });
                }
            }
        );
    } else {
        $('#comments_warning1').fadeIn(1000, function () {
            $(this).fadeOut(1000);
        });
    }
};
// init
$(function(){
    // onclick event handlers
    $('#photo_preview .photo_wrp').click(function (event) {
        event.preventDefault();
        return false;
    });
    $('#photo_preview').click(function (event) {
        closePhotoPreview();
    });
    $('#photo_preview img.close').click(function (event) {
        closePhotoPreview();
    });
    // display photo preview ajaxy
    $('.container .photo img').click(function (event) {
        if (event.preventDefault) event.preventDefault();
        getPhotoPreviewAjx($(this).attr('id'));
    });
})

Обратите внимание на то, что мы используем jQuery в нашем сценарии.

Шаг 4. CSS

В конечном итоге мы должны стилизовать наши элементы страницы (наш контейнер с фотографиями, область предварительного просмотра с комментариями):

css/main.css

/* project styles */
.container {
    border: 1px solid #111111;
    color: #000000;
    margin: 20px auto;
    overflow: hidden;
    padding: 15px;
    position: relative;
    text-align: center;
    width: 1090px;
    -moz-border-radius: 5px;
    -ms-border-radius: 5px;
    -o-border-radius: 5px;
    -webkit-border-radius: 5px;
    border-radius: 5px;
}
.photo {
    border: 1px solid transparent;
    float: left;
    margin: 4px;
    overflow: hidden;
    padding: 4px;
    white-space: nowrap;
    /* CSS3 Box sizing property */
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    -o-box-sizing: border-box;
    box-sizing: border-box;
    /* CSS3 transition */
    -moz-transition: border 0.2s ease 0s;
    -ms-transition: border 0.2s ease 0s;
    -o-transition: border 0.2s ease 0s;
    -webkit-transition: border 0.2s ease 0s;
    transition: border 0.2s ease 0s;
}
.photo:hover {
    border-color: #444;
}
.photo img {
    cursor: pointer;
    width: 200px;
}
.photo p, .photo i {
    display: block;
}
.photo p {
    font-weight: bold;
}
/* preview styles */
#photo_preview {
    background-color: rgba(0, 0, 0, 0.7);
    bottom: 0;
    color: #000000;
    display: none;
    left: 0;
    overflow: hidden;
    position: fixed;
    right: 0;
    top: 0;
    z-index: 10;
}
.photo_wrp {
    background-color: #FAFAFA;
    height: auto;
    margin: 100px auto 0;
    overflow: hidden;
    padding: 15px;
    text-align: center;
    vertical-align: middle;
    width: 1000px;
    -moz-border-radius: 5px;
    -ms-border-radius: 5px;
    -o-border-radius: 5px;
    -webkit-border-radius: 5px;
    border-radius: 5px;
}
.close {
    cursor: pointer;
    float: right;
}
.pleft {
    float: left;
    overflow: hidden;
    position: relative;
    width: 600px;
}
.pright {
    float: right;
    position: relative;
    width: 360px;
}
.preview_prev, .preview_next {
    cursor: pointer;
    margin-top: -64px;
    opacity: 0.5;
    position: absolute;
    top: 50%;
    -moz-transition: opacity 0.2s ease 0s;
    -ms-transition: opacity 0.2s ease 0s;
    -o-transition: opacity 0.2s ease 0s;
    -webkit-transition: opacity 0.2s ease 0s;
    transition: opacity 0.2s ease 0s;
}
.preview_prev:hover, .preview_next:hover {
    opacity: 1;
}
.preview_prev {
    left: 20px;
}
.preview_next {
    right: 40px;
}
/* comments styles */
#comments form {
    margin: 10px 0;
    text-align: left;
}
#comments table td.label {
    color: #000;
    font-size: 13px;
    padding-right: 3px;
    text-align: right;
    width: 105px;
}
#comments table label {
    color: #000;
    font-size: 16px;
    font-weight: normal;
    vertical-align: middle;
}
#comments table td.field input, #comments table td.field textarea {
    border: 1px solid #96A6C5;
    font-family: Verdana,Arial,sans-serif;
    font-size: 16px;
    margin-top: 2px;
    padding: 6px;
    width: 250px;
}
#comments_list {
    margin: 10px 0;
    text-align: left;
}
#comments_list .comment {
    border-top: 1px solid #000;
    padding: 10px 0;
}
#comments_list .comment:first-child {
    border-top-width:0px;
}
#comments_list .comment span {
    font-size: 11px;
}

ДЕМОИсходные файлы

Дополнительная информация

RATTING:
(0 голосов)

Медиа