22. Creating jquery function display loading image in view page

 

22. Creación de función jQuery para mostrar imagen de carga en la página de visualización

¡Hola y bienvenido de nuevo!

En esta lección vamos a mejorar la experiencia de usuario agregando feedback visual durante la traducción. Cuando el usuario hace click en "Translate", mostraremos una animación de carga, ocultaremos contenido antiguo y proporcionaremos scroll automático. ¡Es como tener un asistente visual que te dice "estoy trabajando en ello"!


🎯 ¿Qué vamos a implementar?

Vamos a crear un sistema de feedback visual que:

  1. Muestra animación de carga mientras ChatGPT procesa

  2. Oculta traducciones anteriores temporalmente

  3. Hace scroll automático para mostrar el loader

  4. Muestra errores de forma clara y visible

  5. Previene múltiples clicks durante el procesamiento


🔍 Problema que resolvemos

Sin feedback visual:

  • Usuario hace click en "Translate"

  • Nada parece ocurrir por 2-3 segundos

  • Usuario piensa: "¿Funcionó? ¿Debo hacer click otra vez?"

  • Riesgo de clicks múltiples = múltiples cargos de API

Con feedback visual:

  • Usuario hace click → Inmediatamente ve animación

  • Mensaje claro: "Translating...." ✅

  • Scroll automático al loader ✅

  • Usuario entiende: "Está procesando, debo esperar" ✅

  • Mejor experiencia, menos errores ✅


🛠️ Implementación completa

Parte 1: Manejo de errores en PHP

php

<!-- ERROR HERE -->

<?php if(isset($error) or $error = $whisperObj->errors()): ?>    

    <div class="flex bg-red-100 border border-red-400 text-red-700 px-4 py-2 rounded relative">

        <strong class="font-bold">Error:</strong>

        <span class="block sm:inline"><?php echo $error; ?></span>

    </div>

<?php endif; ?>

Explicación:

php

// Dos formas de detectar errores:

// 1. $error variable local (seteada en POST handling)

// 2. $whisperObj->errors() (errores de la clase)


// Operador OR asigna y evalúa:

// Si isset($error) es TRUE → muestra $error

// Si es FALSE → asigna $whisperObj->errors() a $error y evalúa


Parte 2: Estructura HTML del loader y traducción

html

<!-- Sección de traducción existente (se ocultará) -->

<li id="translateContent" class="flex flex-col my-5 w-full">

    <h3 class="text-2xl"><?php echo $file->lang; ?></h3>

    <div><?php echo $file->translated; ?></div>

</li>


<!-- Loader (inicialmente oculto) -->

<li class="flex flex-col my-5 w-full">

    <div id="loader" class="hidden flex flex-col text-center flex-1 rounded-xl w-full">

        <div class="flex items-center flex-col">

            <img class="w-20 animate-pulse mt-20" src="frontend/images/loader.gif"/>

            <span class="animate-pulse">Translating....</span>

        </div>

    </div>

</li>

Nota: El loader tiene class="hidden" inicialmente.


Parte 3: JavaScript/jQuery para el feedback visual

javascript

<script>

    // Cuando se hace click en el botón "Translate"

    $("#translateBtn").click(function(event){

        

        // 1. Obtener la altura total del contenedor scrollable

        let scrollHeight = $("#scroll")[0].scrollHeight;

        

        // 2. Ocultar traducción anterior (si existe)

        $("#translateContent").addClass("hidden");

        

        // 3. Mostrar animación de carga

        $("#loader").removeClass("hidden");

        

        // 4. Hacer scroll automático al final para mostrar el loader

        $("#scroll").animate({scrollTop: scrollHeight}, "fast");

    });

</script>


📝 Explicación detallada del código JavaScript

1. $("#translateBtn").click(function(event){ ... })

  • $() → Selector de jQuery (similar a document.querySelector())

  • #translateBtn → Selecciona elemento con id="translateBtn"

  • .click() → Escucha eventos de click

  • function(event) → Función que se ejecuta al hacer click

Analogía: Como configurar una alarma que suena (ejecuta función) cuando alguien presiona un botón.

2. let scrollHeight = $("#scroll")[0].scrollHeight

javascript

// Paso a paso:

$("#scroll")          // Selecciona div con id="scroll"

[0]                   // Convierte objeto jQuery a DOM nativo

.scrollHeight         // Propiedad nativa: altura total (incluyendo contenido no visible)

¿Qué es scrollHeight?

text

┌─────────────────────────────────────────┐

│ CONTENEDOR #scroll (height: 500px)      │

├─────────────────────────────────────────┤

│ • Texto original (visible)              │ ← 200px visibles

│ • Loader (oculto inicialmente)          │

│ • Espacio adicional                     │ ← 300px scrollables

│                                         │

│ ALTURA TOTAL (scrollHeight): ~800px     │

└─────────────────────────────────────────┘

3. $("#translateContent").addClass("hidden")

  • #translateContent → Traducción anterior

  • .addClass("hidden") → Agrega clase CSS hidden

  • hidden en Tailwind → display: none (oculta elemento)

¿Por qué ocultar traducción anterior?

  • Evita confusión durante nueva traducción

  • Espacio visual limpio para el loader

  • Claridad: "Estoy generando NUEVA traducción"

4. $("#loader").removeClass("hidden")

  • #loader → Div con animación GIF

  • .removeClass("hidden") → Remueve la clase que lo ocultaba

  • Resultado → Loader se hace visible inmediatamente

5. $("#scroll").animate({scrollTop: scrollHeight}, "fast")

javascript

{

    scrollTop: scrollHeight  // Mover scroll a esta posición (abajo)

}, "fast"                   // Duración: "fast" (~200ms), "slow" (~600ms)

¿Qué hace scrollTop?

text

ANTES:                     DESPUÉS:

┌─────────────────┐       ┌─────────────────┐

│ [Texto]         │       │ [Espacio]       │

│ [Espacio]       │       │ [Espacio]       │

│ [Loader👁️]     │       │ [Loader 👀]    │ ← scrollTop mueve aquí

└─────────────────┘       └─────────────────┘

scrollTop: 0              scrollTop: scrollHeight


🎨 Diagrama visual del flujo

text

┌─────────────────────────────────────────────────────────────┐

│                FLUJO DE INTERACCIÓN COMPLETO                │

├─────────────────────────────────────────────────────────────┤

│ ESTADO INICIAL:                                             │

│ ┌─────────────────────────────────────────────────────┐    │

│ │ ✅ Texto original visible                            │    │

│ │ ❌ Loader oculto (class="hidden")                    │    │

│ │ ❓ Traducción anterior (visible si existe)           │    │

│ │ [Select Language] [Translate 👆]                     │    │

│ └─────────────────────────────────────────────────────┘    │

│                    ↓ Usuario hace click                     │

│                                                            │

│ JAVASCRIPT EJECUTA:                                        │

│ 1. Oculta traducción anterior → .addClass("hidden")       │

│ 2. Muestra loader → .removeClass("hidden")                │

│ 3. Scroll automático al loader                            │

│                                                            │

│ ESTADO DURANTE TRADUCCIÓN:                                 │

│ ┌─────────────────────────────────────────────────────┐    │

│ │ ✅ Texto original visible                            │    │

│ │ ✅ Loader VISIBLE 🔄 "Translating...."               │    │

│ │ ❌ Traducción anterior OCULTA                        │    │

│ │ [Select Language] [Translate (deshabilitado)]       │    │

│ └─────────────────────────────────────────────────────┘    │

│                    ↓ ChatGPT responde (2-3 seg)            │

│                                                            │

│ PHP: Guarda en BD y redirige                              │

│                                                            │

│ ESTADO FINAL:                                              │

│ ┌─────────────────────────────────────────────────────┐    │

│ │ ✅ Texto original visible                            │    │

│ │ ❌ Loader oculto                                     │    │

│ │ ✅ NUEVA traducción visible                          │    │

│ │ [Select Language] [Translate 👆]                     │    │

│ └─────────────────────────────────────────────────────┘    │

└─────────────────────────────────────────────────────────────┘


🔧 Mejoras adicionales recomendadas

1. Deshabilitar botón durante procesamiento:

javascript

$("#translateBtn").click(function(event){

    // Deshabilitar botón para evitar clicks múltiples

    $(this).prop("disabled", true).text("Translating...");

    

    // Resto del código...

    

    // Rehabilitar después (en callback de éxito/error)

    // O mejor: dejar que la redirección PHP maneje esto

});

2. Mostrar porcentaje de progreso (simulado):

javascript

let progress = 0;

let progressInterval = setInterval(function(){

    progress += 10;

    $("#loader span").text(`Translating... ${progress}%`);

    if(progress >= 90) clearInterval(progressInterval);

}, 300); // Cada 300ms

3. Animación más suave:

javascript

// En lugar de "fast", usar valores específicos

$("#scroll").animate({

    scrollTop: scrollHeight

}, {

    duration: 400,    // 400 milisegundos

    easing: "swing",  // Aceleración suave

    complete: function(){

        console.log("Scroll completado");

    }

});

4. Prevenir envío de formulario si hay error:

javascript

// Verificar que se seleccionó idioma

let selectedLang = $("#lang").val();

if(!selectedLang){

    event.preventDefault(); // Detener envío

    alert("Please select a language");

    return false;

}


💡 Conceptos importantes de jQuery/JavaScript

jQuery vs JavaScript nativo:

javascript

// jQuery (más simple, compatible):

$("#loader").removeClass("hidden");


// JavaScript nativo (más rápido, moderno):

document.getElementById("loader").classList.remove("hidden");

Manejo de clases CSS:

javascript

// jQuery:

.addClass("clase")      // Agregar

.removeClass("clase")   // Remover

.toggleClass("clase")   // Alternar


// Equivalente CSS nativo:

.classList.add("clase")

.classList.remove("clase")  

.classList.toggle("clase")

Eventos comunes:

javascript

.click()     // Click del mouse

.submit()    // Envío de formulario

.keypress()  // Tecla presionada

.hover()     // Mouse entra/sale

.change()    // Valor cambia (select, input)


🎨 Tailwind CSS classes usadas

css

/* Clases en nuestro loader: */

.hidden           → display: none; (oculto)

.flex             → display: flex;

.flex-col         → flex-direction: column;

.text-center      → text-align: center;

.rounded-xl       → border-radius: 0.75rem;

.w-full          → width: 100%;

.items-center     → align-items: center;

.animate-pulse    → animación de palpitación

.mt-20           → margin-top: 5rem;

.w-20            → width: 5rem;

Animación animate-pulse:

css

/* Tailwind genera: */

@keyframes pulse {

  0%, 100% { opacity: 1; }

  50% { opacity: .5; }

}

.animate-pulse {

  animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;

}


⚠️ Consideraciones y errores comunes

Error: "Cannot read property 'scrollHeight' of undefined"

javascript

// PROBLEMA: $("#scroll") no encuentra el elemento

// SOLUCIÓN: Verificar que existe en el DOM

if($("#scroll").length > 0){

    let scrollHeight = $("#scroll")[0].scrollHeight;

}

Error: Loader no se muestra pero traducción funciona

javascript

// PROBLEMA: PHP redirige antes de que JavaScript ejecute

// SOLUCIÓN: Redirección ocurre demasiado rápido

// MEJORA: Agregar pequeño delay o AJAX

Error: Scroll no funciona en móviles

javascript

// PROBLEMA: Algunos dispositivos móviles tienen issues con scroll programático

// SOLUCIÓN: Usar polyfill o verificar compatibilidad

$("#scroll")[0].scrollTop = scrollHeight; // Método nativo


📊 Comparación: Con vs Sin feedback visual

Aspecto

Sin feedback

Con feedback

Experiencia usuario

Confusa, parece que no funciona

Clara, sabe que está procesando

Clicks múltiples

Alto riesgo (múltiples cargos API)

Bajo riesgo (loader indica ocupado)

Tiempo percibido

"Lento" (no hay indicación)

"Rápido" (ve progreso)

Errores

Usuario repite acción innecesariamente

Usuario espera pacientemente

Profesionalismo

Parece aplicación amateur

Parece aplicación profesional


🔍 Ejemplo práctico paso a paso

Usuario traduce "Hola mundo" a Francés:

  1. Antes del click:

    • Loader: class="hidden" (oculto)

    • Traducción anterior: visible o no existe

    • Botón: "Translate" (habilitado)

  2. Click en "Translate":

  3. javascript

// JavaScript ejecuta:

// 1. $("#translateContent").addClass("hidden") → Oculta traducción vieja

// 2. $("#loader").removeClass("hidden") → Muestra animación

  1. // 3. Scroll al final → Usuario ve el loader inmediatamente

  2. Durante traducción (2-3 seg):

    • Usuario ve: 🔄 "Translating...." con animación

    • No puede hacer click nuevamente (botón envió formulario)

    • Entiende que debe esperar

  3. Después de traducción:

    • PHP guarda en BD y redirige

    • Nueva página carga con traducción visible

    • Loader oculto nuevamente

    • Ciclo completo


🎯 Puntos Clave para Recordar

  1. addClass() / removeClass() → Controlan visibilidad de elementos

  2. scrollHeight → Altura total del contenido (incluso no visible)

  3. scrollTop → Posición actual del scroll (0 = arriba)

  4. .animate() → Animaciones suaves en jQuery

  5. Feedback inmediato → Mejora experiencia de usuario significativamente

  6. Prevención de múltiples submits → Loader indica "ocupado"

  7. jQuery selectores → $("#id") para ID, $(".clase") para clases


📋 Cuestionario de Repaso

Pregunta 1:

¿Qué hace $("#loader").removeClass("hidden")?
a) Remueve la clase CSS 'hidden' haciendo visible el elemento
b) Agrega una nueva clase llamada 'hidden'
c) Oculta el elemento con ID 'loader'
d) Cambia el texto dentro del elemento

Pregunta 2:

¿Por qué necesitamos obtener scrollHeight antes de animar el scroll?
a) Para saber cuánto tiempo durará la animación
b) Para saber hasta dónde hacer scroll (altura total del contenido)
c) Para calcular la velocidad de la animación
d) Para verificar si el elemento existe

Pregunta 3:

¿Qué beneficio principal tiene mostrar el loader inmediatamente al hacer click?
a) Hace que la traducción sea más precisa
b) Proporciona feedback visual inmediato al usuario
c) Acelera el proceso de traducción
d) Evita errores en la API de ChatGPT

Pregunta 4:

¿Por qué ocultamos la traducción anterior (#translateContent) al iniciar nueva traducción?
a) Para liberar memoria en el navegador
b) Para evitar confusión y mostrar claramente que se está generando nueva traducción
c) Porque jQuery requiere ocultar elementos antes de mostrar otros
d) Para hacer la página más rápida

Pregunta 5:

¿Qué representa [0] en $("#scroll")[0].scrollHeight?
a) El primer carácter del ID del elemento
b) Convierte el objeto jQuery a elemento DOM nativo
c) Accede al primer hijo del elemento #scroll
d) Es un índice para arrays de jQuery


📝 Respuestas del Cuestionario

  1. a) removeClass("hidden") remueve la clase CSS que oculta el elemento (en Tailwind, .hidden es display: none), haciéndolo visible inmediatamente.

  2. b) scrollHeight nos da la altura TOTAL del contenido dentro del contenedor scrollable. Usamos este valor en scrollTop para mover el scroll hasta el final y asegurar que el loader sea visible.

  3. b) El feedback visual inmediato es crucial en UX. Sin él, usuarios piensan que la aplicación no responde y hacen click múltiples veces, causando errores y cargos adicionales de API.

  4. b) Ocultar la traducción anterior clarifica al usuario que se está generando una NUEVA traducción (posiblemente en otro idioma), no que se está recargando la existente.

  5. b) jQuery retorna una colección (aunque sea un solo elemento). [0] accede al primer elemento DOM nativo de esa colección, permitiendo usar propiedades nativas como scrollHeight.


🚀 Lo que viene después

¡Excelente! Ahora tenemos:
✅ Feedback visual profesional durante traducciones
✅ Manejo de errores visible para usuarios
✅ Experiencia de usuario pulida y profesional
✅ Prevención de errores por clicks múltiples

✨ ¡Felicidades! Has completado una aplicación web completa y profesional con:

  1. Subida de archivos multimedia

  2. Transcripción automática con Whisper AI

  3. Base de datos para persistencia

  4. Traducción multilingüe con ChatGPT

  5. Interfaz responsive y moderna

  6. Feedback visual profesional

  7. Manejo de errores robusto

Tu aplicación está lista para producción. Los usuarios pueden subir audio/video, obtener texto transcrito, traducir a múltiples idiomas y acceder a su historial completo. ¡Un proyecto impresionante que combina múltiples tecnologías modernas!


Comentarios

Entradas más populares de este blog

1-7. Transforma tu audio a texto

10. Haz que tu asistente hable

8. NUEVO - Solución si tu micrófono no está captando tu audio