Кнопка зворотного зв’язку у вигляді піднятого великого пальця з анімацією без будь-якої бібліотеки анімацій
Створіть віджет зворотного зв’язку у вигляді пальця, піднятого вгору, та пальця, опущеного вниз, із анімацією pop на ключових кадрах, активними станами та викликом fetch, використовуючи лише 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.