Cómo añadir barra de progreso de lectura en blog WordPress usando solo CSS y Javascript

Cómo añadir barra de progreso de lectura en blog WordPress usando solo CSS y Javascript

Aquí tienes una versión avanzada, con animación suave, cambio de color según el progreso, soporte para “prefiere reducir movimiento”, cálculo sobre el área de lectura (artículo) y alto rendimiento (usa transform + requestAnimationFrame). Incluye todo: PHP para WordPress, CSS y JavaScript.


Código HTML para añadir en WordPress

Código para functions.php (tema hijo recomendado):

<?php
/**
 * Barra de progreso de lectura: salida HTML, estilos y encolado del JS.
 */

// 1) Inserta el contenedor de la barra lo antes posible en el <body>
function rp_output_progress_bar() {
    // role="progressbar" + atributos ARIA para accesibilidad
    echo '<div id="reading-progress" role="progressbar" aria-label="Progreso de lectura" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"></div>';
}
add_action('wp_body_open', 'rp_output_progress_bar'); // requiere WP 5.2+. Si tu tema no llama a wp_body_open, cambia a wp_footer.

// 2) Estilos (puedes mover esto a style.css si prefieres)
function rp_progress_bar_styles() {
    ?>
    <style>
      /* Contenedor de la barra */
      #reading-progress{
        position: fixed;
        top: 0;
        left: 0;
        height: 6px;
        width: 100%;
        /* Usamos transform en lugar de width por rendimiento (GPU-friendly) */
        transform: scaleX(0);
        transform-origin: left center;
        /* Color dinámico con variable, por defecto azul */
        --bar-color: #0ea5e9;
        background: linear-gradient(90deg, var(--bar-color), var(--bar-color));
        box-shadow: 0 2px 10px rgba(0,0,0,.18), 0 0 12px color-mix(in srgb, var(--bar-color) 60%, transparent);
        border-bottom-right-radius: 12px;
        border-top-right-radius: 12px;
        z-index: 9999;
        transition: transform 120ms ease-out, background-color 200ms linear, box-shadow 200ms linear;
        /* Suave relieve visual */
        backdrop-filter: saturate(120%) blur(0.5px);
      }

      /* Modo reducido de movimiento */
      @media (prefers-reduced-motion: reduce){
        #reading-progress{ transition: none; }
      }

      /* Evita tapar menús fijos altos: desplázala un poco si tienes admin bar */
      @media screen and (min-width: 782px){
        body.admin-bar #reading-progress{ top: 32px; }
      }
    </style>
    <?php
}
add_action('wp_head', 'rp_progress_bar_styles');

// 3) Encola el script (crea el archivo /reading-progress.js en tu tema hijo)
function rp_enqueue_progress_script() {
    // Dependencias vacías; en pie de página para no bloquear
    wp_enqueue_script(
        'reading-progress',
        get_stylesheet_directory_uri() . '/reading-progress.js',
        array(),
        '1.0.0',
        true
    );

    // Selector del área de lectura: ajusta si tu tema usa otros
    $config = array(
        'selectors' => array('article', '.entry-content', 'main', '#primary'),
        'minHeight' => 400 // píxeles mínimos para activar barra
    );
    wp_add_inline_script('reading-progress', 'window.__READING_PROGRESS_CONFIG__ = ' . wp_json_encode($config) . ';', 'before');
}
add_action('wp_enqueue_scripts', 'rp_enqueue_progress_script');

Código Javascript para añadir al Wordpress

Contenido de reading-progress.js (guárdalo en la raíz del tema hijo o donde apuntaste arriba):

(function () {
  'use strict';

  const cfg = (window.__READING_PROGRESS_CONFIG__) || {};
  const selectors = cfg.selectors || ['article', '.entry-content', 'main', '#primary'];
  const MIN_HEIGHT = typeof cfg.minHeight === 'number' ? cfg.minHeight : 400;

  const bar = document.getElementById('reading-progress');
  if (!bar) return;

  // Encuentra el contenedor de lectura más pertinente
  function findReadingTarget() {
    for (const sel of selectors) {
      const el = document.querySelector(sel);
      if (el && el.getBoundingClientRect) {
        const h = el.scrollHeight || el.offsetHeight || 0;
        if (h >= MIN_HEIGHT) return el;
      }
    }
    // fallback: documento entero
    return document.documentElement;
  }

  let target = findReadingTarget();

  // Métricas para cálculo de progreso
  let startY = 0;      // posición inicial del contenedor
  let endY = 0;        // posición final "leíble" (parte inferior menos viewport)
  let range = 1;       // endY - startY (evitar division by zero)
  let rafPending = false;

  function clamp01(x) { return Math.max(0, Math.min(1, x)); }

  function recalc() {
    const rect = target === document.documentElement
      ? { top: 0, height: document.documentElement.scrollHeight }
      : target.getBoundingClientRect();

    // top absoluto del target
    const targetTop = (target === document.documentElement)
      ? 0
      : (window.scrollY + rect.top);

    const targetHeight = (target === document.documentElement)
      ? document.documentElement.scrollHeight
      : target.scrollHeight || rect.height;

    const viewport = window.innerHeight || document.documentElement.clientHeight || 0;

    startY = targetTop;
    endY   = targetTop + targetHeight - viewport;
    range  = Math.max(1, endY - startY);
    update(); // actualiza inmediatamente tras recalcular
  }

  function colorForProgress(p) {
    // De verde (#22c55e) a amarillo (#eab308) a rojo (#ef4444)
    // p: 0..1. Interpolamos en dos tramos.
    const stops = [
      { p: 0.0, r: 34,  g:197, b: 94  }, // verde  #22c55e
      { p: 0.5, r:234, g:179, b: 8   }, // amarillo #eab308
      { p: 1.0, r:239, g: 68, b: 68  }  // rojo    #ef4444
    ];

    let c1 = stops[0], c2 = stops[2];
    if (p <= 0.5) { c1 = stops[0]; c2 = stops[1]; p = p / 0.5; }
    else          { c1 = stops[1]; c2 = stops[2]; p = (p - 0.5) / 0.5; }

    const r = Math.round(c1.r + (c2.r - c1.r) * p);
    const g = Math.round(c1.g + (c2.g - c1.g) * p);
    const b = Math.round(c1.b + (c2.b - c1.b) * p);
    return `rgb(${r}, ${g}, ${b})`;
  }

  function update() {
    rafPending = false;
    const y = window.scrollY || window.pageYOffset || 0;
    const p = clamp01((y - startY) / range);

    // Escala de la barra
    bar.style.transform = `scaleX(${p})`;

    // Color y brillo dinámicos
    const c = colorForProgress(p);
    bar.style.setProperty('--bar-color', c);
    bar.style.boxShadow = `0 2px 10px rgba(0,0,0,.18), 0 0 ${8 + 16*p}px ${c}`;

    // ARIA
    const val = Math.round(p * 100);
    bar.setAttribute('aria-valuenow', String(val));
  }

  function onScroll() {
    if (!rafPending) {
      rafPending = true;
      window.requestAnimationFrame(update);
    }
  }

  // Recalcula al cargar y cuando cambie el layout
  window.addEventListener('load', recalc);
  window.addEventListener('resize', () => { recalc(); });
  document.addEventListener('readystatechange', () => {
    if (document.readyState === 'interactive') recalc();
  });

  // Observa cambios en el tamaño del objetivo (contenido que se expande, comentarios, etc.)
  let resizeObs;
  if ('ResizeObserver' in window) {
    resizeObs = new ResizeObserver(() => recalc());
    if (target && target !== document.documentElement) resizeObs.observe(target);
  }

  // Si el target no era válido al principio, intenta encontrarlo cuando el DOM cambie
  const mo = new MutationObserver(() => {
    const newTarget = findReadingTarget();
    if (newTarget !== target) {
      if (resizeObs && target && target !== document.documentElement) resizeObs.unobserve(target);
      target = newTarget;
      if (resizeObs && target && target !== document.documentElement) resizeObs.observe(target);
      recalc();
    }
  });
  mo.observe(document.documentElement, { childList: true, subtree: true });

  // Scroll listener
  window.addEventListener('scroll', onScroll, { passive: true });

  // Primer cálculo inmediato
  recalc();
})();

Añadir código CSS para WordPress

Alternativa si prefieres llevar los estilos a style.css (en vez de inyectarlos desde PHP):

/* style.css (opcional si no usas la inyección en wp_head) */
#reading-progress{
  position: fixed;
  top: 0;
  left: 0;
  height: 6px;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left center;
  --bar-color: #0ea5e9;
  background: linear-gradient(90deg, var(--bar-color), var(--bar-color));
  box-shadow: 0 2px 10px rgba(0,0,0,.18), 0 0 12px color-mix(in srgb, var(--bar-color) 60%, transparent);
  border-bottom-right-radius: 12px;
  border-top-right-radius: 12px;
  z-index: 9999;
  transition: transform 120ms ease-out, background-color 200ms linear, box-shadow 200ms linear;
  backdrop-filter: saturate(120%) blur(0.5px);
}
@media (prefers-reduced-motion: reduce){
  #reading-progress{ transition: none; }
}
body.admin-bar #reading-progress{ top: 32px; }

Notas rápidas:

  • Si tu tema no llama a wp_body_open, cambia el add_action('wp_body_open', ...) por add_action('wp_footer', ...) para imprimir la barra al final del body.
  • Ajusta los selectores en __READING_PROGRESS_CONFIG__ si tu tema usa otro contenedor principal de artículo.
  • El color progresa de verde→amarillo→rojo a medida que avanzas, y la animación se suaviza; se desactiva si el usuario tiene “reducir movimiento”.

Si quieres que lo adapte a tu tema concreto (por ejemplo, Astra, GeneratePress, Divi, etc.) o que la barra evite solaparse con un header fijo personalizado no lo dudes y suscribete a algunos de nuestros Planes de Optimizacion WordPress. Nos vemos!

Categoría: CSS personalizado para WordPress

Scroll al inicio