Кнопка обратной связи с анимированным жестом «палец вверх» без использования каких-либо библиотек анимаций
Создайте всплывающий анимированный элемент обратной связи с символами «лайк» и «дизлайк», включающий состояния активности и запрос к серверу, используя только HTML, CSS и несколько строк JavaScript.
Источники: Creating an Interactive Thumbs Up Feedback Button with HTML, CSS and JavaScript
Кнопки «лайк» и «дизлайк» — один из самых дешевых способов узнать у пользователей, помогла ли им страница, ответ или функция. Обычная реализация заключается лишь в изменении цвета фона при нажатии, что легко ускользнуть от внимания, если больше ничего на странице не меняется. В этом руководстве создается более разнообразная версия: при нажатии на кнопку «лайк» иконка на короткое время заменяется сверкающим смайликом, появляющимся с анимацией масштабирования и поворота, после чего возвращается к обычному виду выделенной иконки «лайк». Вы увидите структуру кода, CSS-стили, JavaScript для последовательного изменения состояний элемента, а также то, что необходимо устранить перед выпуском решения.
Что делает этот вижуал
Интерфейс содержит две круглые кнопки: одну для положительного, другую — для отрицательного отзыва. Нажатие на кнопку с положительным отзывом запускает короткую последовательность действий:
- Иконка «лайк» заменяется смайликом, окруженным сверкающими символами.
Подобные микроинтеракции делают интерфейс отзывчивым без необходимости использования фреймворков анимаций.
Маркировка двух кнопок
Каждая кнопка получает идентификатор для скриптов, общий класс для стилизации и отдельный класс для задания собственного цвета в активном состоянии. Иконки представляют собой символы эмодзи внутри кнопок:
<div class="feedback">
<button
id="thumbsUp"
class="feedback-btn thumbs-up"
aria-label="Thumbs up">
👍🏻
</button>
<button
id="thumbsDown"
class="feedback-btn thumbs-down"
aria-label="Thumbs down">
👎🏻
</button>
</div>
Здесь важен атрибут aria-label. Без него скрин-читер будет озвучивать само название эмодзи (или ничего полезного), поэтому этот атрибут даёт каждой кнопке понятное доступное название. Если вы хотите, чтобы вспомогательные технологии также знали, какой вариант выбран, добавьте атрибут aria-pressed и обновляйте его вместе с классом active.
Круглые кнопки с реакцией при наведении
Базовый стиль задаёт размер, устраняет стандартный внешний вид, центрирует иконку с помощью flexbox и превращает кнопку в круг:
.feedback-btn {
width: 52px;
height: 52px;
border: 1px solid #ddd;
background: #fff;
border-radius: 50%;
cursor: pointer;
font-size: 25px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.25s ease;
}
border-radius: 50% для квадратного элемента делает его круглым, а transition сглаживает последующие изменения; указание конкретных свойств вместо all предотвращает анимацию тех элементов, которые вы не хотели анимировать.
Правило для наведения освещает фон и немного увеличивает размер кнопки, чтобы указатель мыши получал мгновенную обратную связь:
.feedback-btn:hover {
background: #f5f5f5;
transform: scale(1.08);
}
Ключевые кадры, отвечающие за эффект «поп»
Интересным моментом является переход от символа «лайк» к сверкающему смайлику. Это реализуется с помощью правила @keyframes под названием sparkle:
@keyframes sparkle {
0% {
transform: scale(0.4) rotate(-20deg);
opacity: 0;
}
35% {
transform: scale(1.4) rotate(10deg);
opacity: 1;
}
65% {
transform: scale(1) rotate(-5deg);
}
100% {
transform: scale(1) rotate(0deg);
}
}
Если рассматривать это как временную шкалу: на уровне 0% содержимое сжимается до 40%, наклоняется на 20 градусов назад и становится невидимым. На уровне 35% масштаб увеличивается до 140%, происходит небольшой наклон в противоположном направлении, и содержимое становится полупрозрачным. На уровне 65% оно возвращается к нормальному размеру с небольшим противофазным вращением, а на уровне 100% остается в своем естественном размере и угле наклона. Именно этот скачок масштаба, за которым следует стабилизация, создает эффект «всплытия». Обратите внимание, что это правило определяет лишь движение; отдельный класс .sparkle, применяющий его с помощью атрибута animation: sparkle 0.8s ease forwards, приведен в полном примере в конце.
Порядок выполнения состояний в JavaScript
Обработчик клика для кнопки «лайк» координирует замену содержимого, применение класса анимации и достижение конечного состояния:
thumbsUp.addEventListener("click", function () {
if (thumbsUp.classList.contains("active")) {
return;
}
thumbsUp.innerHTML = "✨😊✨";
thumbsUp.classList.add("sparkle");
setTimeout(function () {
thumbsUp.innerHTML = "👍";
thumbsUp.classList.remove("sparkle");
thumbsUp.classList.add("active");
}, 900);
});
Сначала выполняется проверка: если у кнопки уже есть класс active, обработчик возвращает, поэтому анимация не может быть повторена путем многократных кликов. Затем содержимое заменяется на смайлик:
thumbsUp.innerHTML = "✨😊✨";
и добавляется класс, отвечающий за запуск анимации ключевых кадров:
thumbsUp.classList.add("sparkle");
Используется функция setTimeout с задержкой в 900 миллисекунд, что немного больше, чем 0,8 секунды анимации; после этого восстанавливается символ «палец вверх», удаляется класс sparkle, а добавляется класс active:
thumbsUp.innerHTML = "👍";
Содержимое никогда не является вводом пользователя, поэтому здесь безопасно использовать innerHTML, хотя textContent лучше отражает замысел.
Выделение выбранного варианта
Класс active обеспечивает визуальное подтверждение. Положительная обратная связь отображается на светло-зеленом фоне с зеленой рамкой:
.thumbs-up.active {
background: #e8f5e9;
border-color: #4caf50;
}
Отрицательная обратная связь использует тот же шаблон в красном цвете:
.thumbs-down.active {
background: #ffebee;
border-color: #f44336;
}
Разные цвета позволяют сразу увидеть, какой вариант был выбран, но не стоит полагаться только на цвет; атрибут aria-pressed помогает пользователям, которые не могут их различить.
Обеспечение взаимоисключаемости вариантов
Иконка «минус» использует более простой способ взаимодействия без анимации. Она переключает свой собственный класс active, сбрасывает положительное состояние, и если теперь выбран этот вариант, возвращает иконку «плюс» к её первоначальному виду:
thumbsDown.addEventListener("click", function () {
thumbsDown.classList.toggle("active");
thumbsUp.classList.remove("active");
if (thumbsDown.classList.contains("active")) {
thumbsUp.innerHTML = "👍🏻";
}
});
Очистка другой кнопки предотвращает одновременное отображение обоих вариантов как выбранных. Остается один крайний случай: если пользователь нажимает кнопку «лайк», а затем «дизлайк» в течение 900 мс, срабатывает заданный таймер и вновь активирует кнопку «лайк». Чтобы устранить эту проблему, необходимо сохранить идентификатор таймера и вызвать clearTimeout в обработчике нажатия «дизлайк». Также стоит отметить, что действие «лайк» нельзя отменить путем повторного нажатия на неё, в то время как «дизлайк» меняет состояние; следует сделать их поведение согласованным.
Почему небольшая анимация того стоит
Статическое изменение цвета указывает на то, что что-то произошло; короткая анимация (клик, движение, подтверждение) создаёт впечатление, что интерфейс отреагировал. Это особенно важно для инлайн-обратной связи, когда на странице нет других сигналов о успешном выполнении действия. Этот подход подходит для форм обратной связи по курсам, оценок в разделах FAQ и базах знаний, инструментов поддержки, отзывов на продукты, опросов, сайтов документации, ответов чат-ботов на основе ИИ и внутренних инструментов.
Соблюдайте параметр prefers-reduced-motion, отключая ключевые кадры для пользователей, которые просят меньше анимации.
Отправка оценки на сервер
В текущем виде виджет является исключительно визуальным. В реальном продукте необходимо сохранять выбор пользователя, и процесс расширяется за счёт отправки запроса:
User clicks 👍
↓
Animation plays
↓
Feedback recorded
↓
Database updated
↓
User sees confirmation
Подойдёт любой сервер — PHP, Node.js, Python или что-то ещё. Из обработчика клика оценку нужно отправить на соответствующий конечный пункт:
fetch("/api/feedback", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
feedback: "positive"
})
});
Сервер может хранить страницу, пользователя, ответ, временную метку и тип обратной связи. Проверьте значение response.ok, поскольку функция fetch отклоняет запросы только при сетевых ошибках, и решите, следует ли запускать анимацию оптимистично (быстрее, но с необходимостью отката при неудачной сохранении) или только после успешного выполнения.
Полная страница
Здесь всё находится в одном файле: класс .sparkle, эффект масштабирования в состоянии нажатия :active и обработчик для отображения лайка, снимающий выбор минуса:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Feedback Buttons</title>
<style>
.feedback {
display: flex;
gap: 12px;
align-items: center;
}
.feedback-btn {
width: 52px;
height: 52px;
border: 1px solid #ddd;
background: #fff;
border-radius: 50%;
cursor: pointer;
font-size: 25px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.25s ease;
}
.feedback-btn:hover {
background: #f5f5f5;
transform: scale(1.08);
}
.feedback-btn:active {
transform: scale(0.92);
}
/* Sparkle animation */
.sparkle {
animation: sparkle 0.8s ease forwards;
}
@keyframes sparkle {
0% {
transform: scale(0.4) rotate(-20deg);
opacity: 0;
}
35% {
transform: scale(1.4) rotate(10deg);
opacity: 1;
}
65% {
transform: scale(1) rotate(-5deg);
}
100% {
transform: scale(1) rotate(0deg);
}
}
/* Filled thumbs up */
.thumbs-up.active {
background: #e8f5e9;
border-color: #4caf50;
}
.thumbs-down.active {
background: #ffebee;
border-color: #f44336;
}
</style>
</head>
<body>
<div class="feedback">
<button
id="thumbsUp"
class="feedback-btn thumbs-up"
aria-label="Thumbs up">
👍🏻
</button>
<button
id="thumbsDown"
class="feedback-btn thumbs-down"
aria-label="Thumbs down">
👎🏻
</button>
</div>
<script>
const thumbsUp = document.getElementById("thumbsUp");
const thumbsDown = document.getElementById("thumbsDown");
thumbsUp.addEventListener("click", function () {
// Prevent repeated animation
if (thumbsUp.classList.contains("active")) {
return;
}
// Step 1: Sparkling smiley
thumbsUp.innerHTML = "✨😊✨";
thumbsUp.classList.add("sparkle");
// Step 2: Change to filled thumbs up
setTimeout(function () {
thumbsUp.innerHTML = "👍";
thumbsUp.classList.remove("sparkle");
thumbsUp.classList.add("active");
}, 900);
// Remove thumbs down selection
thumbsDown.classList.remove("active");
});
thumbsDown.addEventListener("click", function () {
thumbsDown.classList.toggle("active");
// Remove thumbs up selection
thumbsUp.classList.remove("active");
if (thumbsDown.classList.contains("active")) {
thumbsUp.innerHTML = "👍🏻";
}
});
</script>
</body>
</html>
Итоги
Для качественного управления обратной связью достаточно HTML, CSS и нескольких строк JavaScript. Ключевые кадры обеспечивают эффект всплытия, последовательности замены классов по времени управляют состояниями, а итоговый выделенный значок подтверждает сделанный выбор.
- Храните логику анимации в CSS, позволяя JavaScript лишь переключать классы и содержимое.
aria-pressed и режим с уменьшенной скоростью анимаций, чтобы веб-инструмент оставался доступным.Тот же подход применим к иконкам SVG, анимациям Lottie, звуковым сигналам и более сложным рабочим процессам, основанным на API.