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; }