Skip to content

Análisis de Media Queries en CSS para Optimizar el Rendimiento

Published:

Cuando auditamos la performance de un sitio web, una de las primeras tareas que solemos hacer es buscar CSS no utilizado. Herramientas como el panel Coverage de las Chrome DevTools son fantásticas para esto. Sin embargo, a menudo pasamos por alto un culpable silencioso: el CSS oculto dentro de media queries que no se aplican al viewport actual.

Aunque estas reglas no se apliquen visualmente, el navegador igualmente debe descargar, parsear y procesar todo el código CSS, incluyendo esas media queries inactivas. Este trabajo extra, aunque parezca menor, suma y puede degradar la experiencia de nuestra web, especialmente en dispositivos móviles con recursos limitados.

Pero el impacto no se limita solo a la carga inicial del fichero. Una de las ventajas más importantes de separar el CSS por media queries se manifiesta durante el recálculo de estilos, un proceso clave en las webs interactivas y modernas.

Cuando el navegador parsea el CSS, construye un “árbol de reglas” en el CSSOM. Si una @media query está inactiva (como una de escritorio en un móvil), el navegador es lo suficientemente inteligente como para ignorar esa rama completa de reglas durante los recálculo de estilos. Esto significa que cuando cambiamos clases con JavaScript, el motor de renderizado no pierde tiempo evaluando cientos o miles de reglas de escritorio que no aplican. Solo trabaja con el conjunto de reglas activas, haciendo que estas actualizaciones de estilo sean mucho más rápidas y fluidas. Esta optimización es crucial para una buena experiencia de usuario en aplicaciones dinámicas.

Hoy vamos a ver cómo podemos identificar este CSS “oculto” y qué podemos hacer para optimizarlo, todo ello gracias a un útil WebPerf Snippet.

El coste oculto de las media queries inactivas

Imaginemos una hoja de estilos “mobile-first”. Tenemos nuestros estilos base y luego, a medida que el viewport crece, añadimos estilos que apliquen a viewports mayores dentro de media queries con min-width.

/* Estilos base (móvil) */
.card {
  width: 100%;
  padding: 1rem;
}

/* Estilos para tablets y más grandes */
@media (min-width: 768px) {
  .card {
    width: 50%;
    padding: 1.5rem;
  }
  .sidebar {
    display: block; /* El sidebar solo existe en desktop */
  }
  /* ... y cientos de reglas más ... */
}

Un dispositivo móvil descargará y analizará todas esas reglas para min-width: 768px, aunque nunca las vaya a utilizar. El navegador construye el CSSOM (CSS Object Model) completo en memoria, y solo después descarta las reglas que no coinciden con el viewport actual. Este proceso consume tiempo de CPU y memoria, retrasando el renderizado de la página.

La pregunta clave es: ¿cuánto CSS estamos enviando a nuestros usuarios móviles que solo es para escritorio? Saberlo nos da una idea clara del potencial de optimización.

La solución: Un Snippet para analizar las Media Queries

Para responder a esa pregunta, vamos a utilizar un snippet de JavaScript que podemos ejecutar directamente en la consola de las DevTools. Este script analiza todas las hojas de estilo de la página, busca las @media queries que aplican a viewports más grandes que un punto de ruptura (breakpoint) que definamos y nos da un resumen detallado.

Este es el WebPerf Snippet original en el que se basa este artículo.

El núcleo de la solución es la función analyzeCSSMediaQueries. Aquí está el código para que entendamos cómo funciona:

function analyzeCSSMediaQueries(minWidth = 768) {
  const isBiggerThanBreakpoint = mediaQuery => {
    const minWidthRegex = /min-width:\s*(\d+)(px|em|rem)/;
    const matches = mediaQuery.match(minWidthRegex);
    if (!matches) return false;

    const queryMinWidth = parseInt(matches[1], 10);
    return queryMinWidth >= minWidth && !mediaQuery.includes("max-width");
  };

  const results = {
    inline: { totalBytes: 0, queries: [] },
    files: { totalBytes: 0, queries: {} },
    summary: { totalQueries: 0, totalClasses: 0, totalProperties: 0 },
  };

  for (const sheet of document.styleSheets) {
    try {
      for (const rule of sheet.cssRules) {
        if (
          rule instanceof CSSMediaRule &&
          isBiggerThanBreakpoint(rule.conditionText)
        ) {
          const text = rule.cssText;
          const byteSize = new Blob([text]).size;
          const numClasses = (text.match(/\./g) || []).length;
          const numProperties = (text.match(/;/g) || []).length;

          results.summary.totalQueries++;
          results.summary.totalClasses += numClasses;
          results.summary.totalProperties += numProperties;

          const source = sheet.href || "inline";
          const queryData = {
            media: rule.conditionText,
            classes: numClasses,
            properties: numProperties,
            size: byteSize,
          };

          if (source === "inline") {
            results.inline.totalBytes += byteSize;
            results.inline.queries.push(queryData);
          } else {
            results.files.totalBytes += byteSize;
            if (!results.files.queries[source]) {
              results.files.queries[source] = [];
            }
            results.files.queries[source].push(queryData);
          }
        }
      }
    } catch (e) {
      if (e instanceof DOMException && e.name === "SecurityError") {
        console.warn(
          `[INFO] No se pudo acceder a la hoja de estilos de origen cruzado: ${sheet.href}`
        );
      } else {
        console.error(
          `Error procesando la hoja de estilos ${sheet.href || "inline"}:`,
          e
        );
      }
    }
  }
  return results;
}

// Cómo mostrar los resultados
function displayResults(results) {
  console.clear();
  console.log(
    "%cAnálisis de Media Queries CSS",
    "font-weight: bold; font-size: 1.5em; color: #3b82f6;"
  );
  console.log("--------------------------------------");
  console.log(
    `Total de @media queries para desktop analizadas: ${results.summary.totalQueries}`
  );
  console.log(`Total de clases: ${results.summary.totalClasses}`);
  console.log(`Total de propiedades: ${results.summary.totalProperties}`);
  console.log(
    `Ahorro potencial en móvil (estimado): ${(results.inline.totalBytes + results.files.totalBytes) / 1024} KB`
  );
  // ... resto de la lógica para mostrar los resultados en la consola
}

La lógica es sencilla pero muy efectiva:

  1. Recorre todas las hojas de estilo (document.styleSheets).
  2. Dentro de cada una, busca reglas de tipo CSSMediaRule.
  3. Filtra las que son para viewports más grandes que el minWidth especificado.
  4. Para cada una, calcula el tamaño, número de clases y propiedades.
  5. Agrupa los resultados por si son de un fichero externo (.css) o si están en línea (<style>).

Cómo usar el Snippet e interpretar los resultados

Usarlo es muy fácil:

  1. Abre las DevTools de tu navegador (F12 o Cmd+Opt+I).
  2. Ve a la pestaña “Consola”.
  3. Pega el script completo (la función analyzeCSSMediaQueries y la que muestra los resultados).
  4. Ejecuta la función:
// Analizar con el breakpoint por defecto de 768px
displayResults(analyzeCSSMediaQueries());

// O con un breakpoint personalizado, por ejemplo 1024px
displayResults(analyzeCSSMediaQueries(1024));

Verás una salida en la consola parecida a esta:

Análisis de Media Queries CSS
--------------------------------------
Total de @media queries para desktop analizadas: 85
Total de clases: 1230
Total de propiedades: 2450
Ahorro potencial en móvil (estimado): 55.7 KB

--- Ficheros Externos (.css) ---
Ahorro total: 45.2 KB
- /assets/main.css: 40.1 KB
  - @media (min-width: 1024px): 850 clases, 1700 props, 35.5 KB
  - @media (min-width: 768px): 50 clases, 100 props, 4.6 KB
- /assets/vendor.css: 5.1 KB
  - @media (min-width: 992px): 30 clases, 60 props, 5.1 KB

--- Estilos Inline (<style>) ---
Ahorro total: 10.5 KB
  - @media (min-width: 1200px): 100 clases, 200 props, 8.2 KB
  - @media (min-width: 768px): 20 clases, 40 props, 2.3 KB

Lo más importante aquí es el “Ahorro potencial en móvil”. Este número nos dice cuántos KB de CSS se está descargando un usuario móvil que nunca va a necesitar. Un ahorro de 55 KB, como en el ejemplo, puede tener un impacto muy notable en el tiempo de carga y renderizado, lo que ayudará a mejorar métricas como FCP (First Contentful Paint) y la Core Web Vital LCP (Largest Contentful Paint).

De la analítica a la optimización

Una vez que tenemos los datos, podemos tomar decisiones informadas. Aquí van algunas estrategias:

  1. Dividir el CSS: La estrategia más efectiva. Crea una hoja de estilos para escritorio (desktop.css) y cárgala de forma condicional usando el atributo media. Los navegadores modernos optimizan esto: aunque descargarán el fichero en dispositivos móviles, lo harán con una prioridad muy baja y sin bloquear el renderizado de la página.

    <!-- CSS principal para móvil -->
    <link rel="stylesheet" href="main.css" />
    
    <!-- CSS para escritorio. En móvil se descarga con baja prioridad sin bloquear el renderizado. -->
    <link rel="stylesheet" href="desktop.css" media="(min-width: 1024px)" />
    
  2. CSS Crítico en línea: Mueve los estilos esenciales para el “above-the-fold” en móvil a una etiqueta <style> en el <head>. Esto mejora drásticamente el FCP (First Contentful Paint).

  3. Revisa tu enfoque: Si la cantidad de CSS para escritorio es enorme, quizás sea un síntoma de que el enfoque no es “mobile-first” de verdad. Refactorizar hacia un modelo donde los estilos base son para móvil y solo se añaden modificaciones para escritorio puede ser la solución a largo plazo.

Un Caso Real: Analizando Renfe

Para ilustrar el potencial de este análisis en un escenario real, hemos ejecutado el snippet en la web de Renfe. En la captura de pantalla se puede apreciar que el “Ahorro potencial en móvil” supera los 284 KB. Se trata de una cantidad considerable que, de optimizarse, podría mejorar de forma notable los tiempos de carga y la experiencia de los usuarios y usuarias que acceden desde dispositivos móviles.

Captura de pantalla de la web de Renfe en vista móvil dentro de las herramientas de desarrollador de Chrome. A la izquierda se muestra la interfaz móvil del sitio con un menú, un buscador de trenes y una promoción de Black Friday. A la derecha, en la consola, aparece un informe de análisis de media queries CSS indicando el tamaño total de reglas, clases y propiedades, así como el cálculo de ahorro potencial de CSS para móviles, con un detalle de estilos inline y de archivos externos.

Conclusión

El rendimiento web está en los detalles. Optimizar nuestras media queries es uno de esos detalles que a menudo se nos escapa pero que puede marcar una gran diferencia. Con un simple snippet de JavaScript, hemos visto cómo podemos obtener una visión clara de cuánto CSS estamos malgastando en dispositivos móviles.

Este análisis nos da el poder de pasar de la intuición a la acción, aplicando optimizaciones que tienen un impacto medible en la experiencia de nuestros usuarios y usuarias. Porque al final, un sitio más rápido es un sitio que convierte más y que los usuarios y usuarias disfrutan más usando.


Previous Post
Optimiza el renderizado con content-visibility
Next Post
Más Allá de INP: LCP y el 90% invisible del rendimiento