Strona główna / Artykuły

Przycisk opinii w formie animowanego kciuka w górę bez żadnej biblioteki animacji

Stwórz widget z opiniami w postaci kciuka w górę i w dół z animacją pop na podstawie kluczowych klatek, stanami aktywnymi oraz żądaniem danych, używając wyłącznie HTML, CSS i kilku linijek JavaScript.

Wydawca: REACTAPP.TOP7 min czytania1559 słów

Źródła: Creating an Interactive Thumbs Up Feedback Button with HTML, CSS and JavaScript

Klawisze z podniesionym i opuszczonym kciukiem to jeden z najtańszych sposobów na zapytanie użytkowników, czy strona, odpowiedź lub funkcja im pomogły. Zwykła implementacja polega jedynie na zmianie koloru tła po kliknięciu, co łatwo przeoczyć, jeśli nic innego na stronie się nie zmienia. W tym przewodniku stworzymy nieco bardziej rozbudowaną wersję: po kliknięciu na klawisz z podniesionym kciukiem ikona zostaje na chwilę zastąpiona błyszczącym emotikonem, który pojawia się dzięki animacji skali i obrotu, a następnie wraca do standardowej postaci podniesionego kciuka. Zobaczysz kod strukturalny, pliki CSS oraz JavaScript odpowiedzialne za sekwencjonowanie stanów, a także elementy, które należy poprawić przed publikacją.

Czym zajmuje się ten widget

Interfejs zawiera dwa okrągłe klawisze – jeden do pozytywnej, a drugi do negatywnej opinii. Kliknięcie w klawisz pozytywny uruchamia krótką sekwencję działań:

  1. Ikona podniesionego kciuka zostaje zastąpiona emotikonem otoczonym błyszczącymi znakami.
  • Animacja kluczowych klatek pokazuje to element z lekkim powiększeniem i obróceniem.
  • Po krótkiej przerwie uśmiechnięta twarz znów zmienia się w zwykły gest podnoszenia kciuka.
  • Przycisk przechodzi w stan aktywny, z podświetleniem.
  • Takie mikrointerakcje sprawiają, że interfejs wydaje się responsywny bez konieczności używania frameworków animacyjnych.

    Tagi do dwóch przycisków

    Każdy przycisk ma identyfikator do skryptowania, wspólną klasę do stylizacji oraz klasę modyfikującą jego własny kolor aktywny. Ikony to znaki emoji umieszczone wewnątrz przycisków:

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

    Tutaj istotna jest właściwość aria-label. Bez niej odczytarka ekranu ogłosiłaby samo nazwę emoji (lub nic użytecznego), więc ta etykieta nadaje każdemu przyciskowi jasną, dostępną nazwę. Jeśli chcesz, aby technologie wspomagające wiedziały również, która opcja jest wybrana, dodaj aria-pressed i aktualizuj ją razem z klasą active.

    Klawisze okrągłe z reakcją na przesunięcie kursora

    Bazowy styl ustala rozmiar, usuwa domyślny wygląd, centruje ikonę za pomocą flexbox i sprawia, że klawisz ma kształt koła:

    .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% w elementie kwadratowym tworzy kółko, a transition zapewnia płynne zmiany; podanie konkretnych właściwości zamiast all pozwala uniknąć animacji elementów, których nie chcieliśmy.

    Zasada reakcji na przesunięcie kursora rozjaśnia tło i nieco powiększa klawisz, dzięki czemu kursor otrzymuje natychmiastową informację zwrotną:

    .feedback-btn:hover {
        background: #f5f5f5;
        transform: scale(1.08);
    }
    

    Kluczowe klatki animacji stojące za efektem „pop”

    Ciekawy moment to zmiana z gestu kciuka w górę na iskrzącego się emotikonę. Jest to obsługiwane przez zasadę @keyframes o nazwie 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);
        }
    }
    

    Jako wykres czasu: przy 0% treść jest zmniejszona do 40%, przechylona o 20 stopni do tyłu i niewidoczna. Przy 35% osiąga skalę 140%, z lekkim przechyłem w przeciwnym kierunku i pełną przejrzystością. Przy 65% wraca do normalnego rozmiaru z niewielką przeciwwrotką, a przy 100% pozostaje w swoim naturalnym rozmiarze i kącie. To przekroczenie wartości, po którym następuje ustabilizowanie, daje efekt „pop”. Należy pamiętać, że ta zasada definiuje jedynie ruch; osobna klasa .sparkle, która ją implementuje za pomocą animation: sparkle 0.8s ease forwards, znajduje się w kompletnym przykładzie na końcu.

    Sekwencjonowanie stanów w JavaScript

    Obsługa kliknięcia odpowiada za koordynację zamiany treści, klasę animacji oraz ostateczny stan:

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

    Zaczyna się od sprawdzenia: jeśli przycisk ma już klasę active, funkcja obsługi wraca, dzięki czemu animacja nie może być odtwarzana wielokrotnym klikaniem. Następnie zastępuje się treść emotikoną:

    thumbsUp.innerHTML = "✨😊✨";
    

    i dodaje klasę, która uruchamia kluczowe klatki animacji:

    thumbsUp.classList.add("sparkle");
    

    Używany jest setTimeout na 900 milisekund, co jest nieco dłużej niż czas trwania animacji wynoszący 0,8 sekundy; po tym czasie przywraca się znak kciuka w górę, usuwa się klasę sparkle i dodaje klasę active:

    thumbsUp.innerHTML = "👍";
    

    Treść nigdy nie pochodzi od użytkownika, więc użycie innerHTML jest bezpieczne, chociaż textContent lepiej oddaje intencję.

    Wyświetlanie wybranej opcji

    Klasa active służy do wizualnego potwierdzenia. Pozytywna informacja zwrotna ma jasnozielony tło i zieloną obwódkę:

    .thumbs-up.active {
        background: #e8f5e9;
        border-color: #4caf50;
    }
    

    Ujemna informacja zwrotna wykorzystuje ten sam wzorzec w kolorze czerwonym:

    .thumbs-down.active {
        background: #ffebee;
        border-color: #f44336;
    }
    

    Różne kolory pozwalają od razu zobaczyć, która opcja została wybrana, ale nie należy polegać wyłącznie na kolorze; aria-pressed pomaga użytkownikom, którzy nie potrafią ich odróżnić.

    Zapewnienie wzajemnej wykluczalności opcji

    Przycisk z kciukiem w dół wykorzystuje prostszą interakcję bez animacji. Zmienia swój własny klasę active, usuwa stan pozytywny, a jeśli jest teraz wybrany, przywraca ikonę z kciukiem w górę do jej pierwotnego kształtu:

    thumbsDown.addEventListener("click", function () {
        thumbsDown.classList.toggle("active");
        thumbsUp.classList.remove("active");
        if (thumbsDown.classList.contains("active")) {
            thumbsUp.innerHTML = "👍🏻";
        }
    });
    

    Usuwanie wyboru z drugiego przycisku zapobiega temu, by obie opcje wyglądały na wybrane jednocześnie. Pozostaje jeden przypadek graniczny: jeśli użytkownik kliknie przycisk „plus” a następnie „minus” w ciągu 900 ms, nadal zostanie uruchomiony timer i ponownie aktywowany będzie przycisk „plus”. Aby zlikwidować tę lukę, należy przechowywać identyfikator timera i wywołać clearTimeout w obsłudze przycisku „minus”. Należy również zauważyć, że aktywny przycisk „plus” nie może zostać anulowany poprzez jego ponowne kliknięcie, podczas gdy przycisk „minus” zmienia stan; należy to uporządkować, aby oba funkcjonowały spójnie.

    Dlaczego mała animacja jest warta wysiłku

    Zmiana koloru w trybie statycznym wskazuje, że coś się wydarzyło; krótka animacja (kliknięcie, ruch, potwierdzenie) sprawia wrażenie, że interfejs zareagował. Jest to szczególnie ważne w przypadku informacji zwrotnych inline, gdy nic innego na stronie nie sygnalizuje sukcesu. Nadaje się do form ocen kursów, ocen w FAQ i bazach wiedzy, narzędzi wsparcia, recenzji produktów, ankiet, stron dokumentacyjnych, odpowiedzi chatbotów AI oraz narzędzi wewnętrznych.

    Szanuj zmienną prefers-reduced-motion, wyłączając klatki kluczowe dla użytkowników, którzy proszą o mniej animacji.

    Wysyłanie oceny do serwera backend

    Jak jest napisane, widget ma charakter wyłącznie wizualny. W rzeczywistym produkcie konieczne jest przechowywanie wyboru, a proces rozszerza się o żądanie:

    User clicks 👍
           ↓
    Animation plays
           ↓
    Feedback recorded
           ↓
    Database updated
           ↓
    User sees confirmation
    

    Może to być dowolny backend – PHP, Node.js, Python lub coś innego. Z obsługi kliknięcia należy wysłać ocenę do odpowiedniego punktu końcowego:

    fetch("/api/feedback", {
        method: "POST",
        headers: {
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            feedback: "positive"
        })
    });
    

    Serwer może przechowywać stronę, użytkownika, odpowiedź, datę i typ informacji zwrotnej. Sprawdź wartość response.ok, ponieważ funkcja fetch odrzuca żądanie tylko w przypadku błędów sieciowych, i zdecyduj, czy animacja ma być uruchamiana w trybie optymistycznym (szybciej, ale wymagającego cofnięcia w razie niepowodzenia zapisu) czy tylko po odniesieniu sukcesu.

    Cała strona

    Tutaj znajduje się wszystko w jednym pliku, w tym klasa .sparkle, efekt skaliowania w stanie naciśnięcia :active oraz funkcja obsługi przycisku „polubić”, która usuwa wybór opcji „nie polubić”:

    <!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>
    

    Podsumowanie

    Doskonała kontrola informacji zwrotnej wymaga jedynie HTML, CSS oraz kilku linijek JavaScriptu. Kluczowe klatki czasowe odpowiadają za efekt wybuczenia, sekwencje zmian klas w określonych momentach sterują stanami, a ostateczny podświetlony ikona potwierdza dokonany wybór.

    • Zachowaj logikę animacji w CSS i pozwól JavaScriptowi jedynie przełączać klasy oraz treść.
  • Niech system śledzi czas wygaśnięcia, aby szybkie kliknięcia w drugi przycisk nie mogły utrzymać obu opcji aktywnych.
  • Dodaj właściwość aria-pressed oraz rozwiązanie awaryjne typu reduced-motion, aby widget pozostał dostępny.
  • Traktuj wywołanie do serwera jako część interakcji: radź sobie z błędami i decyduj między optymistyczną a potwierdzoną informacją zwrotną.
  • Taki sam podejście można zastosować do ikon SVG, animacji Lottie, dźwięków oraz bardziej zaawansowanych procesów opartych na API.

    Pozycje pokrewne