WhatsApp

  

Blurry Loading

efecto de desenfoque mientras carga (vanilla HTML/CSS/JS)

Vamos a crear una pantalla de carga que parte con una imagen desenfocada y un texto de porcentaje que va de 0% a 100%. A medida que avanza la carga, el desenfoque disminuye y el texto se desvanece. Es un micro‑proyecto perfecto para practicar transformaciones lineales, filter: blur(), y animaciones con JavaScript sin dependencias. 


1) ¿Qué es el efecto Blurry Loading?

Es un patrón de loading visual que “suaviza” la espera del usuario. En lugar de ver una pantalla vacía, mostramos una versión desenfocada del contenido de fondo y un contador que indica progreso. Al llegar a 100%, el fondo queda nítido y el contador desaparece.

Qué practicas con este proyecto:

  • Manipulación del DOM y setInterval / requestAnimationFrame.

  • Uso de filtros CSS (blur, brightness) y transiciones.

  • Mapeo de rangos (llevar un valor 0→100 a otro rango, p.ej. 30px→0px de blur y 1→0 de opacidad).

2) Demo mínima (copia y pega)

A continuación tienes una versión autosuficiente (un solo archivo). Puedes usar una imagen local (recomendado) o una URL pública ligera. Cambia background-image por la tuya.

<!DOCTYPE html>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Blurry Loading — Demo</title>
<style>
* { box-sizing: border-box; }
:root {
--bg:#0f1115; --txt:#f5f7fa; --muted:#a7b0be;
}
body {
margin: 0; height: 100vh; display: grid; place-items: center;
background: var(--bg); color: var(--txt); font: 16px/1.6 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
overflow: hidden;
}
.wrap {
position: relative; width: 100%; height: 100%;
}
.bg {
position: absolute; inset: 0; background-size: cover; background-position: center;
/* ⚠️ Reemplaza esta imagen por la tuya (local o CDN) */
background-image: url('https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?q=80&w=1600&auto=format&fit=crop');
filter: blur(30px); /* inicia con 30px y va bajando a 0 */
transform: scale(1.05); /* ligero zoom para evitar bordes del blur */
}
.scrim {
position: absolute; inset: 0; background: #0007; /* leve oscurecimiento opcional */
}
.counter {
position: absolute; inset: 0; display: grid; place-items: center; font-weight: 700;
font-size: clamp(2rem, 7vw, 5rem); letter-spacing: .02em; color: var(--txt);
opacity: 1; /* va a 0 cuando cargamos */
user-select: none;
}
.hint {
position: absolute; left: 50%; bottom: 24px; transform: translateX(-50%);
font-size: .95rem; color: var(--muted);
}
</style>
</head>
<body>
<div class="wrap">
<div class="bg" id="bg"></div>
<div class="scrim"></div>
<div class="counter" id="counter">0%</div>
<div class="hint">Cargando…</div>
</div>
<script>
// Utilidad: mapea un valor de un rango a otro
const scale = (num, inMin, inMax, outMin, outMax) =>
((num - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
const bg = document.getElementById('bg');
const counter = document.getElementById('counter');
let load = 0; // 0 → 100
const tick = () => {
load++;
if (load > 100) return; // detén en 100
counter.textContent = load + '%';
// 30px → 0px de blur
const blur = scale(load, 0, 100, 30, 0);
bg.style.filter = `blur(${blur}px)`;
// 1 → 0 de opacidad
const opacity = scale(load, 0, 100, 1, 0);
counter.style.opacity = opacity;
// Usa rAF para animación suave (≈60fps)
if (load < 100) requestAnimationFrame(tick);
else counter.remove(); // opción: quita el contador al terminar
};
// Arranca
requestAnimationFrame(tick);
</script>
</body>
</html>


Notas rápidas

  • Para evitar “bordes” feos con blur, aplicamos un ligero scale(1.05) sobre el fondo.

  • Con requestAnimationFrame obtienes una animación más suave y eficiente que con setInterval.

  • La función scale() te permite trasladar fácilmente el rango 0–100 del contador a cualquier otro rango (blur, opacidad, brillo, etc.).

2.1) Código por separado (HTML / CSS / JS)

Si prefieres tener los archivos individuales, usa esta estructura:

Index.html

<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Blurry Loading — Demo</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<div class="wrap">
<div class="bg" id="bg"></div>
<div class="scrim"></div>
<div class="counter" id="counter">0%</div>
<div class="hint">Cargando…</div>
</div>
<script src="app.js"></script>
</body>
</html>


Styles.css

* { box-sizing: border-box; }
:root { --bg:#0f1115; --txt:#f5f7fa; --muted:#a7b0be; }
body {
margin: 0; height: 100vh; display: grid; place-items: center;
background: var(--bg); color: var(--txt);
font: 16px/1.6 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
overflow: hidden;
}
.wrap { position: relative; width: 100%; height: 100%; }
.bg {
position: absolute; inset: 0; background-size: cover; background-position: center;
/* ⚠️ Cambia esta imagen por la tuya (local o CDN) */
background-image: url('https://images.unsplash.com/photo-1500530855697-b586d89ba3ee?q=80&w=1600&auto=format&fit=crop');
filter: blur(30px); /* inicia con 30px y va bajando a 0 */
transform: scale(1.05); /* evita bordes del blur */
}
.scrim { position: absolute; inset: 0; background: #0007; }
.counter {
position: absolute; inset: 0; display: grid; place-items: center; font-weight: 700;
font-size: clamp(2rem, 7vw, 5rem); letter-spacing: .02em; color: var(--txt);
opacity: 1; /* va a 0 cuando cargamos */
user-select: none;
}
.hint { position: absolute; left: 50%; bottom: 24px; transform: translateX(-50%); font-size: .95rem; color: var(--muted); }


app.js

// Utilidad: mapea un valor de un rango a otro
const scale = (num, inMin, inMax, outMin, outMax) =>
((num - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin;
const bg = document.getElementById('bg');
const counter = document.getElementById('counter');
let load = 0; // 0 → 100
function tick() {
  load++;
  if (load > 100) return; // detén en 100
  counter.textContent = load + '%';
  // 30px → 0px de blur
  const blur = scale(load, 0, 100, 30, 0);
  bg.style.filter = `blur(${blur}px)`;
  // 1 → 0 de opacidad
  const opacity = scale(load, 0, 100, 1, 0);
  counter.style.opacity = opacity;
  if (load < 100) requestAnimationFrame(tick);
  else counter.remove(); // opcional: eliminar el contador al terminar
}
// Arrancar animación
requestAnimationFrame(tick);


3) Explicación paso a paso

  1. Marcado: cuatro capas: bg (imagen), scrim (oscurecedor opcional), counter (porcentaje) y un hint.

  2. Estilos:

    • El desenfoque inicia alto (blur(30px)) y lo reducimos a 0.

    • El texto empieza totalmente visible (opacity: 1) y se desvanece a 0.

    • Usamos clamp() para que el tamaño del contador sea fluido en móviles y escritorio.

  3. Lógica:

    • Variable load avanza de 0→100 (un tick por cuadro con requestAnimationFrame).

    • Convertimos load a otros rangos: blur (30→0) y opacidad (1→0) con scale().

    • Al llegar a 100, podemos remover el contador o transicionar a la UI real.

4) Variantes útiles

  • Carga real: Si quieres que el número refleje progreso “real” (fetch de assets), conecta load a eventos de progreso (XHR, fetch con ReadableStreams, progress de <img>/<video> cuando sea viable) y avanza el valor según bytes descargados.

  • Brillo y saturación: además de blur, puedes animar filter: brightness() o saturate() para un efecto más cinematográfico.

  • Placeholder sólido: reemplaza la imagen por un degradado bonito y luego, al llegar a 100, intercambia por la imagen final (evita saltos bruscos si la imagen tarda).

  • Accesibilidad: añade aria-live="polite" al contador si necesitas anunciar progreso a lectores de pantalla. O bien, provee una alternativa textual fuera de pantalla.


5) Buenas prácticas de rendimiento

  • Usa imágenes optimizadas (WebP/AVIF, tamaño acorde al viewport).

  • Evita filter: blur() sobre elementos muy grandes en equipos de bajos recursos; considera reducir el blur inicial (p.ej., 20px en vez de 30px) si notas jank.

  • Mantén el número de capas y sombras al mínimo durante el loading.

6) Integración rápida en tu blog

  • Inserta el snippet anterior como bloque HTML en tu CMS.

  • Si necesitas aislar estilos, pon el demo en un <iframe>.

  • Cambia la fuente de la imagen a una local para evitar flash por CORS o latencias.

7) Checklist de depuración

  • ¿No se ve el desenfoque? Verifica que no haya un backdrop-filter en vez de filter.

  • ¿Ves bordes raros en los extremos? Mantén transform: scale(1.05).

  • ¿Se “salta” el contador? Comprueba que tick() sólo se programe una vez al inicio.

8) Retos para practicar

  1. Añade una barra de progreso radial o lineal sincronizada con load.

  2. Aplica un fade extra del scrim de #0007 a #0000 conforme carga.

  3. Reemplaza el contador por una palabra que cambie: “Cargando…”, “Casi listo…”, “¡Listo!”.

  4. Implementa una API de progreso real (por ejemplo, precarga de varias imágenes).

9) Ejemplo

Finalmente tu implementación debería de quedar similar a esta


 


Blurry Loading
Paris Minero 20 octubre, 2025
Compartir
Iniciar sesión dejar un comentario

  
Rotating Navigation Animation
Menú lateral con giro “wow” en puro HTML, CSS y JS