Facebook нравится фотогалерея с комментариями
Задумывались ли вы о собственной фотогалерее в стиле Facebook с комментариями? У меня есть и сегодня я подготовил это для вас. Основная идея — когда мы нажимаем на изображения — они всплывают (ajax) с большим изображением слева и разделом комментариев справа. Все изображения находятся в базе данных (mySQL). И, конечно же, мы будем использовать PHP для достижения нашего результата. Кроме того, наша система комментариев будет препятствовать принятию более 1 комментария за 10 минут (чтобы избежать спама).
Live Demo
скачать в упаковке
Теперь загрузите исходные файлы и начните кодировать!
Шаг 1. SQL
Для нашей галереи я подготовил две таблицы 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="http://www.script-tutorials.com/facebook-like-photo-gallery-with-comments/" 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. Также, когда мы публикуем комментарии, мы отправляем этот запрос (чтобы принять новый комментарий) в класс комментариев. Теперь давайте рассмотрим следующий важный файл php:
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" onclick="getPhotoPreviewAjx(\''.$iPrev.'\')"><img src="images/prev.png" alt="prev" /></div>' : '';
$sNextBtn = ($iNext) ? '<div class="preview_next" onclick="getPhotoPreviewAjx(\''.$iNext.'\')"><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;
}
Этот файл возвращает информацию о запрашиваемой фотографии. Это увеличенное изображение, блок с комментариями и кнопками навигации (чтобы открыть предыдущее / следующее изображение ajaxy). Как видите, мы используем класс комментариев, теперь пришло время взглянуть и на него:
классы / 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 onsubmit="return false;">
<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 onclick="submitComment({$i}); return false;">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 в нашем скрипте (надеюсь, вы не забыли, что мы связали библиотеку jQuery в разделе заголовка через службу Google).
Шаг 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;
}