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

Задумывались ли вы о собственном стиле фотогалереи с комментариями например как на facebook? Основная идея — когда мы нажимаем на изображение — оно всплывает (Ajax) с разделом для комментариев. Все изображения хранятся в базе данных MySQL. И конечно мы будем использовать PHP для достижения нашей цели. Кроме того, система комментариев не добавит более 1-го комментария в течении 10 минут (во избежание спама).

Оглавление:
  1. Фотогалерея с комментариями как на Facebook
  2. Шаг 1. SQL
  3. Шаг 2. PHP
  4. index.php
  5. photos_ajx.php
  6. classes/CMyComments.php
  7. Шаг 3. Javascript
  8. js/script.js
  9. Шаг 4. CSS
  10. css/main.css
  11. Заключение

Демо

Второй вариант фотогалереи с комментариями:

Демо

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

к меню ↑

Шаг 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>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>Фотогалерея с комментариями как на Facebook</title>
    <!— Ссылка стили —>
    <link href="css/main.css" rel="stylesheet" type="text/css" />
    <!— Ссылка скрипты —>
<script src="https://www.google.com/jsapi"></script>
<script>
        google.load("jquery", "1.7.1");
    </script>
<script src="js/script.js"></script>
</head>
<body>
    <!— Контейнер с фото —>
    <div class="container">
        <h1>Фото:</h1>
        <?= $sPhotos ?>
    </div>
    <!— Скрытый блок просмотра —>
    <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">тест 1</div>
            <div class="pright">тест 2</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).

Мы используем класс для комментариев, теперь пришло время посмотреть на них:

к меню ↑

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>Комментарий</h2>
    <div id="comments_warning1" style="display:none">Не забудьте заполнить оба поля (Имя и Комментарий)</div>
    <div id="comments_warning2" style="display:none">Вы не можете отправлять больше, чем один комментарий за 10 минут (защита от спама)</div>
    <form onsubmit="return false;">
        <table>
            <tr><td class="label"><label>Ваше имя: </label></td><td class="field"><input type="text" value="" title="Please enter your name" id="name" /></td></tr>
            <tr><td class="label"><label>Комментарии: </label></td><td class="field"><textarea name="text" id="text"></textarea></td></tr>
            <tr><td class="label">&nbsp;</td><td class="field"><button onclick="submitComment({$i}); return false;">Пост с комментарием</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;
}
к меню ↑

Заключение

Уверен, что этот материал будет полезен вам для собственных проектов. Удачи вам в вашей работе!

Статья подготовлена для вас коллективом сайта htmlhook.ru
Оригинал статьи: script-tutorials
Перевел: Виктор Клим
Правила использования материалов сайта!

htmlhook.ru | Скрипты для веб-приложений