Inicio / Artículos

Un botón de retroalimentación con pulgar hacia arriba animado sin ninguna biblioteca de animaciones

Cree un widget de retroalimentación con pulgares arriba y abajo que incluya una animación de aparecimiento mediante keyframes, estados activos y una llamada a fetch, utilizando únicamente HTML, CSS y unas pocas líneas de JavaScript.

Editor: REACTAPP.TOP7 min de lectura1559 palabras

Fuentes: Creating an Interactive Thumbs Up Feedback Button with HTML, CSS and JavaScript

Los botones de pulgar arriba y pulgar abajo son una de las formas más económicas de preguntar a los usuarios si una página, respuesta o función les fue útil. La implementación habitual solo cambia el color de fondo al hacer clic, lo cual es fácil de pasar por alto cuando nada más en la página cambia. Esta guía muestra una versión ligeramente mejorada: al hacer clic en pulgar arriba, el ícono se sustituye brevemente por un smiley brillante que aparece con una animación de escala y rotación, para luego quedar como un pulgar arriba resaltado. Verá el marcado HTML, el CSS, el JavaScript que controla los estados y los aspectos que deben corregirse antes de lanzarlo.

Qué hace el widget

La interfaz cuenta con dos botones circulares, uno para retroalimentación positiva y otro para negativa. Al hacer clic en el botón de retroalimentación positiva, se ejecuta una secuencia breve:

  1. El ícono de pulgar arriba se reemplaza por un smiley rodeado de caracteres brillantes.
  • Una animación de fotogramas clave lo muestra con un ligero crecimiento y rotación.
  • Después de una breve pausa, la carita sonriente vuelve a convertirse en un simple pulgar hacia arriba.
  • El botón pasa a un estado activo y resaltado.
  • Las microinteracciones como esta hacen que una interfaz parezca receptiva sin necesidad de un framework de animaciones.

    Marcado para los dos botones

    Cada botón cuenta con un id para la programación, una clase compartida para el estilo y una clase modificadora para su propio color activo. Los íconos son caracteres emoji dentro de los botones:

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

    El aria-label es importante aquí. Sin él, un lector de pantalla anunciaría el nombre propio del emoji (o nada útil), por lo que la etiqueta proporciona a cada botón un nombre accesible y claro. Si desea que la tecnología de asistencia también sepa qué opción está seleccionada, agregue aria-pressed y actualícelo junto con la clase active.

    Botones redondos con respuesta al pasar el cursor

    El estilo base ajusta el tamaño, elimina el aspecto predeterminado, centra el ícono con flexbox y convierte al botón en un círculo:

    .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% en un elemento cuadrado crea el círculo, y transition suaviza los cambios posteriores; especificar propiedades concretas en lugar de all evita animar elementos que no se pretendía.

    Una regla para el paso del cursor aclara el fondo y agranda ligeramente el botón, de modo que el puntero recibe una respuesta inmediata:

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

    Los keyframes detrás del efecto especial

    El momento interesante es el cambio de un pulgar arriba a una carita sonriente brillante. Esto se gestiona mediante una regla @keyframes llamada 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);
        }
    }
    

    Como línea de tiempo: al 0% el contenido se reduce al 40%, se inclina 20 grados hacia atrás y queda invisible. Al 35% supera el tamaño normal al llegar al 140%, con una ligera inclinación en la dirección opuesta y total opacidad. Al 65% vuelve al tamaño normal con una pequeña rotación contraria, y al 100% mantiene su tamaño y ángulo naturales. Este exceso seguido de un ajuste es lo que se percibe como un “pop”. Tenga en cuenta que esta regla solo define el movimiento; una clase .sparkle separada que lo aplica con animation: sparkle 0.8s ease forwards aparece en el ejemplo completo al final.

    Secuenciar los estados en JavaScript

    El manejador de clics para el ícono de aprobación coordina el cambio de contenido, la clase de animación y el estado final:

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

    Comienza con una verificación: si el botón ya tiene la clase active, el manejador devuelve, de modo que la animación no puede reproducirse al hacer clic repetidamente. Luego reemplaza el contenido por la carita sonriente:

    thumbsUp.innerHTML = "✨😊✨";
    

    y agrega la clase que activa los keyframes:

    thumbsUp.classList.add("sparkle");
    

    Se utiliza setTimeout de 900 milisegundos, un poco más tiempo que la animación de 0.8 segundos; posteriormente se restablece el carácter de pulgar hacia arriba, se elimina sparkle y se agrega active:

    thumbsUp.innerHTML = "👍";
    

    El contenido nunca proviene de la entrada del usuario, por lo que innerHTML es seguro en este caso, aunque textContent expresa mejor la intención.

    Resaltar la opción seleccionada

    La clase active proporciona la confirmación visual. La retroalimentación positiva muestra un fondo verde pálido y un borde verde:

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

    La retroalimentación negativa utiliza el mismo patrón en rojo:

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

    Los colores distintos permiten ver de inmediato qué opción se registró, pero no hay que confiarse únicamente en el color; aria-pressed ayuda a los usuarios que no pueden distinguirlos.

    Mantener las opciones mutuamente excluyentes

    El pulgar hacia abajo utiliza una interacción más simple sin animaciones. Activa su propia clase active, elimina el estado positivo y, si ahora está seleccionado, restablece el ícono del pulgar hacia arriba a su forma original:

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

    Al desactivar el otro botón se evita que ambas opciones parezcan seleccionadas al mismo tiempo. Queda un caso límite: si el usuario hace clic en “me gusta” y luego en “no me gusta” dentro del plazo de 900 ms, el temporizador pendiente sigue activándose y vuelve a habilitar “me gusta”. Es necesario almacenar el identificador del temporizador y llamar a clearTimeout en el manejador de “no me gusta” para cerrar esa brecha. Tenga también en cuenta que un estado de “me gusta” activo no puede anularse haciendo clic en él, mientras que “no me gusta” permite cambiar de estado; haga que ambos funcionen de manera consistente.

    Por qué vale la pena invertir esfuerzo en una pequeña animación

    Un cambio de color estático indica que ocurrió algo; una animación breve (clic, movimiento, confirmación) hace que la interfaz parezca haber respondido. Esto es especialmente importante para las retroalimentaciones en línea, donde nada más en la página señala el éxito. Es adecuado para formularios de retroalimentación en cursos, calificaciones en Preguntas Frecuentes y bases de conocimientos, herramientas de soporte, reseñas de productos, encuestas, sitios de documentación, respuestas de chatbots de IA y herramientas internas.

    Respete la opción prefers-reduced-motion desactivando los fotogramas clave para aquellos usuarios que solicitan menos animaciones.

    Envío de la calificación al backend

    Tal como está implementado, el widget es puramente visual. En un producto real, la elección debe guardarse, y el flujo incluye una solicitud:

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

    Cualquier backend funciona, ya sea PHP, Node.js, Python o otro. Desde el manejador de clics se enviará la calificación a un endpoint:

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

    El servidor puede almacenar la página, el usuario, la respuesta, la marca de tiempo y el tipo de retroalimentación. Verifique response.ok, ya que fetch solo rechaza las solicitudes en caso de errores de red, y decida si la animación se ejecuta de forma optimista (más rápida, pero requiere un retroceso si la guardado falla) o solo después de que tenga éxito.

    La página completa

    Aquí está todo en un único archivo, incluida la clase .sparkle, una escala :active para el estado presionado, y el manejador de “me gusta” que elimina la selección de “no me gusta”:

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

    Conclusión

    Un sistema de control de retroalimentación bien diseñado solo necesita HTML, CSS y unas pocas líneas de JavaScript. Los keyframes proporcionan el efecto de “pop”, las secuencias programadas de cambio de clases ordenan los estados, y el ícono resaltado final confirma la elección.

    • Mantenga la lógica de animación en CSS y deje que JavaScript solo active o desactive clases y contenido.
  • Controlar el tiempo de espera para que los clics rápidos en el otro botón no mantengan ambas opciones activas.
  • Añadir aria-pressed y una opción de reducción de movimiento para que el widget siga siendo accesible.
  • Tratar la llamada al servidor como parte de la interacción: gestionar los fallos y decidir entre una respuesta optimista y una confirmada.
  • El mismo enfoque se aplica a íconos SVG, animaciones Lottie, indicadores sonoros y flujos de trabajo más avanzados basados en API.

    Lecturas relacionadas