Введение
Системы лайков и дизлайков помогают понять реакцию аудитории на материалы и повышают вовлеченность. На uCoz нет встроенного динамического механизма, который обновляет счетчики без перезагрузки страницы, однако это можно реализовать с помощью внешнего сервиса.
В этой статье рассматривается полный рабочий пример системы лайков и дизлайков на uCoz с использованием Firebase Realtime Database. Все работает на чистом JavaScript, без перезагрузки страницы, с хранением данных в облаке. Код дается полностью, без сокращений, и подходит как для вида материалов, так и для страницы отдельного материала.
Что понадобится
- Аккаунт Google для входа в Firebase.
- Доступ к панели управления uCoz и к шаблонам сайта.
- Готовность отредактировать код сайта в разделе управления дизайном и таблицу стилей.
Регистрация проекта в Firebase
Сначала нужно создать проект в Firebase и подключить веб приложение.
- Перейти на сайт Firebase по адресу firebase.google.com.
- Нажать кнопку в правом верхнем углу Войти и авторизоваться через Google.
- После входа нажать Перейти в консоль Firebase.
- В консоли нажать Добавить проект или Создать проект.
- Ввести название проекта, например uCoz Reactions, нажать Далее.
- Если не нужен Google Analytics, можно отключить этот пункт, затем нажать Создать проект.
- Дождаться создания проекта и нажать Продолжить.
Теперь нужно добавить веб приложение.
- В открытом проекте в консоли Firebase выбрать Иконка веб приложения с подписью Web или Добавить приложение и выбрать значок с тегами.
- В поле Название приложения указать любое удобное имя, например uCoz Likes.
- Остальные чекбоксы можно не трогать, если Firebase Hosting не нужен.
- Нажать Зарегистрировать приложение.
- На следующем шаге система покажет код вида:
const firebaseConfig = { apiKey: "...", authDomain: "....firebaseapp.com", databaseURL: "https://...-default-rtdb.firebaseio.com", projectId: "...", storageBucket: "...", messagingSenderId: "...", appId: "..." }; - Скопировать объект firebaseConfig и сохранить, он понадобится в JavaScript на сайте.
Включение Realtime Database и создание правил доступа
Система лайков и дизлайков будет использовать Firebase Realtime Database для хранения счетчиков голосов. Нужно включить эту базу и настроить правила.
- В консоли Firebase открыть раздел Строение данных или Build в боковом меню.
- Выбрать пункт Realtime Database.
- Нажать Создать базу данных.
- Выбрать ближайший регион, например europe west, затем нажать Далее.
- На шаге с правилами можно временно оставить По умолчанию и нажать Включить.
- После создания базы перейти на вкладку Rules в верхней части интерфейса.
Далее нужно заменить правила на свои, чтобы разрешить чтение и запись только в одной ветке uCozReactions.
В поле с правилами удалить текущий текст и вставить:
{
"rules": {
"uCozReactions": {
".read": true,
".write": true
},
".read": false,
".write": false
}
}
После вставки нажать кнопку Publish.
Что это дает:
- Чтение и запись разрешены только для ветки uCozReactions.
- Вся остальная база закрыта для чтения и записи.
- Счетчики лайков и дизлайков для каждого материала будут храниться по пути uCozReactions/ID, где ID это системный идентификатор материала на uCoz.
На стороне Firebase подготовка завершена, дальше идет подключение скрипта на сайте.
Подключение JavaScript на сайте uCoz
Этот скрипт отвечает за инициализацию Firebase, работу с базой и обработку кликов по кнопкам лайка и дизлайка.
На uCoz его удобно подключать через раздел Дизайн - Управление дизайном (шаблоны).
Путь:
Панель управления сайтом → Дизайн → Управление дизайном → Модуль где устанавливаем → Код внизу страниц (перед тегом </body>).
В этот блок нужно вставить следующий код.
Важно заменять значения в firebaseConfig своими данными из консоли Firebase.
<script src="https://www.gstatic.com/firebasejs/8.6.8/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.6.8/firebase-database.js"></script>
<script>
const firebaseConfig = {
apiKey: "ЗАМЕНИТЕ_ЭТИ_ДАННЫЕ",
authDomain: "ЗАМЕНИТЕ_ЭТИ_ДАННЫЕ",
databaseURL: "ЗАМЕНИТЕ_ЭТИ_ДАННЫЕ",
projectId: "ЗАМЕНИТЕ_ЭТИ_ДАННЫЕ"
};
if (!firebase.apps.length) {
firebase.initializeApp(firebaseConfig);
}
const database = firebase.database();
document.addEventListener('DOMContentLoaded', function () {
const containers = document.querySelectorAll('.reactions-container');
containers.forEach(function (container) {
const entryId = container.getAttribute('data-id');
if (!entryId) return;
const reactionsRef = database.ref('uCozReactions/' + entryId);
const storageKey = 'reaction_' + entryId;
const likeBtn = container.querySelector('.reaction-like');
const dislikeBtn = container.querySelector('.reaction-dislike');
const likeCountSpan = container.querySelector('.like-count');
const dislikeCountSpan = container.querySelector('.dislike-count');
if (!likeBtn || !dislikeBtn || !likeCountSpan || !dislikeCountSpan) {
return;
}
let votedType = localStorage.getItem(storageKey);
reactionsRef.on('value', function (snapshot) {
const data = snapshot.val() || { like: 0, dislike: 0 };
likeCountSpan.textContent = data.like;
dislikeCountSpan.textContent = data.dislike;
votedType = localStorage.getItem(storageKey);
if (votedType) {
likeBtn.disabled = true;
dislikeBtn.disabled = true;
likeBtn.classList.remove('voted');
dislikeBtn.classList.remove('voted');
if (votedType === 'like') {
likeBtn.classList.add('voted');
} else if (votedType === 'dislike') {
dislikeBtn.classList.add('voted');
}
} else {
likeBtn.disabled = false;
dislikeBtn.disabled = false;
likeBtn.classList.remove('voted');
dislikeBtn.classList.remove('voted');
}
});
function handleReaction(type) {
if (localStorage.getItem(storageKey)) {
return;
}
reactionsRef.child(type).transaction(function (currentCount) {
return (currentCount || 0) + 1;
}, function (error, committed) {
if (committed) {
localStorage.setItem(storageKey, type);
votedType = type;
likeBtn.disabled = true;
dislikeBtn.disabled = true;
if (type === 'like') {
likeBtn.classList.add('voted');
} else if (type === 'dislike') {
dislikeBtn.classList.add('voted');
}
}
});
}
likeBtn.addEventListener('click', function () {
handleReaction('like');
});
dislikeBtn.addEventListener('click', function () {
handleReaction('dislike');
});
});
});
</script>
После сохранения общий скрипт будет автоматически работать на всех страницах, где есть блок с классом reactions-container и нужными атрибутами.
HTML для вида материалов
На этапе вида материалов можно вывести компактные кнопки лайка и дизлайка внизу каждой карточки.
Куда вставлять:
Дизайн → Управление дизайном → Необходимый модуль → Вид материалов.
Нужно найти место, где выводится футер карточки материала, обычно это блок с датой, автором и счетчиком просмотров, и добавить туда следующий фрагмент.
<span class="meta-item reaction-item"> <div class="reactions-container" data-id="$ID$"> <div class="reaction-area"> <button class="reaction-btn reaction-like" type="button"> <i class="bi bi-hand-thumbs-up"></i> <span class="count like-count">0</span> </button> <button class="reaction-btn reaction-dislike" type="button"> <i class="bi bi-hand-thumbs-down"></i> <span class="count dislike-count">0</span> </button> </div> </div> </span>
Переменная $ID$ здесь обязательна, она связывает конкретный материал с записью в Firebase. Если ее заменить на другую, данные будут храниться отдельно для каждого значения.
HTML для страницы материала и комментариев
На странице отдельно взятого материала можно сделать более крупный и заметный блок с реакциями.
Куда вставлять:
Дизайн → Управление дизайном → нужный модуль → Страница материала и комментариев.
Фрагмент удобно размещать сразу под текстом материала, но до блока с комментариями.
<div class="big-reaction-block mt-4 mb-4"> <div class="reactions-container" data-id="$ID$"> <div class="reaction-area dynamic-icons"> <button class="reaction-btn reaction-like" type="button" title="Лайк"> <i class="bi bi-heart"></i> <span class="count like-count">0</span> </button> <button class="reaction-btn reaction-dislike" type="button" title="Дизлайк"> <i class="bi bi-hand-thumbs-down"></i> <span class="count dislike-count">0</span> </button> </div> <p class="reaction-prompt text-muted small mt-2"> Оцените полезность материала, это помогает улучшать контент </p> </div> </div>
Здесь также используется $ID$, чтобы счетчики на списке и на странице материала совпадали.
Полные CSS стили для обоих вариантов
Чтобы кнопки выглядели аккуратно, используются стили для компактного блока в списке материалов и для большого блока на странице материала.
Куда вставлять:
Дизайн → Управление дизайном → Таблица стилей (CSS) → Основной файл стилей шаблона.
Ниже приведены стили, которые можно вставить как есть. Если на сайте уже подключен Bootstrap, классы вроде text-muted, mt-4 и тому подобные будут работать, а описанные ниже стили только дополнят оформление.
/* Компактные реакции во вьюхе материалов */
.post-card-meta .reaction-area,
.reaction-item .reaction-area {
display: inline-flex;
gap: 6px;
align-items: center;
}
.post-card-meta .reaction-btn,
.reaction-item .reaction-btn {
display: inline-flex;
align-items: center;
padding: 3px 8px;
border: 1px solid #e0e0e0;
border-radius: 4px;
background-color: #fcfcfc;
cursor: pointer;
font-size: 13px;
color: #6c757d;
transition: background-color 0.2s, border-color 0.2s, color 0.2s;
}
.post-card-meta .reaction-btn i,
.reaction-item .reaction-btn i {
font-size: 14px;
margin-right: 4px;
}
.post-card-meta .reaction-btn .count,
.reaction-item .reaction-btn .count {
font-weight: 600;
font-size: 13px;
}
.post-card-meta .reaction-btn:hover:not(.voted),
.reaction-item .reaction-btn:hover:not(.voted) {
background-color: #f8f9fa;
border-color: #d0d0d0;
}
/* Состояние проголосовавшего пользователя */
.post-card-meta .reaction-btn.voted,
.reaction-item .reaction-btn.voted {
pointer-events: none;
}
.post-card-meta .reaction-like.voted,
.reaction-item .reaction-like.voted {
border-color: #28a745;
background-color: #d4edda;
color: #155724;
}
.post-card-meta .reaction-dislike.voted,
.reaction-item .reaction-dislike.voted {
border-color: #dc3545;
background-color: #f8d7da;
color: #721c24;
}
/* Крупный блок реакций на странице материала */
.big-reaction-block {
text-align: center;
padding: 30px 0;
border-top: 1px solid #e9ecef;
border-bottom: 1px solid #e9ecef;
margin-top: 20px;
margin-bottom: 20px;
}
.big-reaction-block .reaction-area.dynamic-icons {
display: flex;
justify-content: center;
gap: 40px;
flex-wrap: wrap;
}
.big-reaction-block .reaction-btn {
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
padding: 10px 20px;
border: 1px solid #ced4da;
border-radius: 50px;
background-color: #ffffff;
cursor: pointer;
transition: background-color 0.2s, border-color 0.2s, transform 0.1s;
}
.big-reaction-block .reaction-btn i {
font-size: 2.4rem;
color: #6c757d;
}
.big-reaction-block .reaction-btn .count {
font-size: 1.8rem;
font-weight: 700;
color: #333333;
}
.big-reaction-block .reaction-btn:hover:not(.voted) {
background-color: #f8f9fa;
border-color: #adb5bd;
transform: translateY(-1px);
}
/* Состояние проголосовавшего пользователя в крупном блоке */
.big-reaction-block .reaction-like.voted {
border-color: #dc3545;
background-color: #fff0f3;
}
.big-reaction-block .reaction-like.voted i,
.big-reaction-block .reaction-like.voted .count {
color: #dc3545;
animation: heart-beat 0.3s ease-in-out;
}
.big-reaction-block .reaction-dislike.voted {
border-color: #007bff;
background-color: #e6f0ff;
}
.big-reaction-block .reaction-dislike.voted i,
.big-reaction-block .reaction-dislike.voted .count {
color: #007bff;
}
/* Анимация для лайка */
@keyframes heart-beat {
0% {
transform: scale(1);
}
50% {
transform: scale(1.15);
}
100% {
transform: scale(1);
}
}
/* Адаптация для мобильных */
@media (max-width: 576px) {
.big-reaction-block .reaction-area.dynamic-icons {
gap: 20px;
}
.big-reaction-block .reaction-btn {
padding: 8px 16px;
}
.big-reaction-block .reaction-btn i {
font-size: 2rem;
}
.big-reaction-block .reaction-btn .count {
font-size: 1.4rem;
}
}
После сохранения этих стилей компактный блок будет аккуратно вписан в мета информацию карточек, а крупный блок на странице материала станет заметным элементом обратной связи.
Как работает система и что важно знать
- Для каждого материала используется свой ID, счетчики в Firebase хранятся по пути uCozReactions/ID.
- Локальное хранилище браузера, localStorage, используется для защиты от повторных голосов с одного браузера, один браузер дает один голос.
- При загрузке страницы скрипт читает данные из Firebase и сразу подставляет актуальные значения, без перезагрузки.
- Кнопки автоматически блокируются после голосования, чтобы предотвратить повторный клик.
- Если пользователь меняет браузер, устройство или очищает localStorage, он сможет проголосовать еще раз, это нормальное поведение для простых систем реакций.
Примечание по установке кода на uCoz
Кратко порядок действий на стороне uCoz:
- Вставить JavaScript с инициализацией Firebase и обработкой реакций в раздел Дизайн - Управление дизайном - Конкретный модуль - Страница архива модуля / Страница материала и комментарие, перед </body> и сохранить.
- Добавить HTML блока с реакциями во Виде материалов в нужное место карточки и сохранить шаблон.
- Добавить HTML крупного блока с реакциями на Странице материала и комментариев и сохранить шаблон.
- Вставить CSS стили в Таблицу стилей шаблона и сохранить изменения.
- Открыть любой материал на сайте, проверить наличие кнопок и убедиться, что при клике счетчики изменяются, а в Firebase в ветке uCozReactions появляются записи с ID материалов.
После выполнения этих шагов на сайте будет работать современная динамическая система лайков и дизлайков с хранением данных в Firebase Realtime Database.
Оцените полезность материала!
Лицензия: CC BY-SA 4.0
Автор: Юрий Герук
Комментарии