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:
Muestra animación de carga mientras ChatGPT procesa
Oculta traducciones anteriores temporalmente
Hace scroll automático para mostrar el loader
Muestra errores de forma clara y visible
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
🔍 Ejemplo práctico paso a paso
Usuario traduce "Hola mundo" a Francés:
Antes del click:
Loader: class="hidden" (oculto)
Traducción anterior: visible o no existe
Botón: "Translate" (habilitado)
Click en "Translate":
javascript
// JavaScript ejecuta:
// 1. $("#translateContent").addClass("hidden") → Oculta traducción vieja
// 2. $("#loader").removeClass("hidden") → Muestra animación
// 3. Scroll al final → Usuario ve el loader inmediatamente
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
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
addClass() / removeClass() → Controlan visibilidad de elementos
scrollHeight → Altura total del contenido (incluso no visible)
scrollTop → Posición actual del scroll (0 = arriba)
.animate() → Animaciones suaves en jQuery
Feedback inmediato → Mejora experiencia de usuario significativamente
Prevención de múltiples submits → Loader indica "ocupado"
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
a) removeClass("hidden") remueve la clase CSS que oculta el elemento (en Tailwind, .hidden es display: none), haciéndolo visible inmediatamente.
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.
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.
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.
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:
Subida de archivos multimedia
Transcripción automática con Whisper AI
Base de datos para persistencia
Traducción multilingüe con ChatGPT
Interfaz responsive y moderna
Feedback visual profesional
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
Publicar un comentario