wandres.dev
LA PERCEPCIÓN DEL TIEMPO · Qué siente el usuario

RAIL: cuatro contextos, cuatro presupuestos

El modelo que traduce los umbrales perceptivos a presupuestos de ingeniería por tipo de actividad, y cómo se relaciona con las Core Web Vitals que lo sucedieron.

⏱ 15 min

RAIL fue el primer intento serio de convertir la investigación sobre percepción en presupuestos que un ingeniero pudiera usar. Su aportación duradera no son los números concretos, que las Core Web Vitals han sustituido como guía oficial, sino la idea de que el usuario tiene expectativas distintas según lo que esté haciendo, y que por tanto no existe un único presupuesto de tiempo para toda la aplicación.

🎯 Al terminar esta lección sabrás
  • Enumerar los cuatro contextos de RAIL y el presupuesto de cada uno.
  • Explicar por qué el presupuesto de respuesta es de 50 ms si el objetivo es 100.
  • Situar cada Core Web Vital frente al contexto RAIL correspondiente.
  • Aplicar el presupuesto de tiempo ocioso al diseño de trabajo diferido.

Los cuatro contextos

RAIL son las iniciales de response, animation, idle y load: respuesta, animación, tiempo ocioso y carga. Cada uno tiene un objetivo y una guía de implementación asociada.

Respuesta: completar la transición iniciada por el usuario en menos de 100 ms. El objetivo viene directamente del umbral perceptivo. La guía de implementación es más estricta que el objetivo: procesar los eventos de entrada en menos de 50 ms. Esa mitad que parece regalada tiene una justificación precisa que veremos en la sección siguiente.

Animación: producir cada fotograma en 10 ms o menos. El presupuesto teórico a 60 Hz es de 16 ms por fotograma, pero el navegador necesita unos 6 ms para su propio trabajo de renderizado y composición. Quedan unos 10 ms para el código de la página. La guía aquí es contundente: en los momentos de animación hay que hacer lo mínimo indispensable, y aprovechar la ventana de 100 ms de respuesta para precalcular todo lo que se pueda antes de que empiece el movimiento.

Conviene recordar que animación en RAIL no significa solo efectos visuales. Incluye el scroll, incluido el desplazamiento por inercia cuando el usuario suelta, y el arrastre, como al desplazar un mapa o hacer un gesto de pellizco. Son las animaciones que más se descuidan porque nadie las escribió: las produce el navegador y las estropea el código de la página.

Tiempo ocioso: maximizarlo, y usarlo en fragmentos de 50 ms o menos. Es el contexto menos intuitivo y el más útil. El objetivo no es un tiempo sino una propiedad: cuanto más ocioso esté el hilo principal, más probable es que responda rápido a la siguiente entrada. Y la guía impone un límite al trabajo diferido: si aprovechas el tiempo muerto para hacer algo, hazlo en trozos de como mucho 50 ms, porque si el usuario interactúa en mitad de uno de esos trozos, tendrá que esperar a que termine.

Carga: entregar contenido y llegar a ser interactivo en menos de 5 segundos en un dispositivo de gama media con conexión lenta, y en menos de 2 segundos en visitas posteriores. Es el contexto que las Core Web Vitals han sustituido con más claridad, porque el LCP mide mejor lo que le importa al usuario que las métricas de interactividad de la época.

ℹ️
RAIL ya no es la guía oficial, y aun así sigue siendo el mejor marco mental

La documentación de Google señala explícitamente que las Core Web Vitals son el enfoque recomendado para definir objetivos de rendimiento, y que sus umbrales difieren de los de RAIL. Es cierto y hay que respetarlo al fijar objetivos. Pero RAIL sigue ganando en una cosa que las Core Web Vitals no cubren: reparte el problema por contexto de actividad, lo cual es la unidad natural para diseñar. Un menú desplegable no se juzga con el LCP; se juzga con el presupuesto de respuesta.

Por qué 50 ms si el objetivo es 100

Esta es la parte de RAIL que más se cita mal y que mejor enseña a pensar en presupuestos.

El objetivo es que el usuario perciba la respuesta en menos de 100 ms desde su acción. Pero el navegador tiene un solo hilo principal, y en el momento en que el usuario pulsa puede estar ocupado con otra cosa. Si tu aplicación hace trabajo diferido en trozos de 50 ms, y el usuario pulsa justo al principio de uno de esos trozos, el evento se queda encolado 50 ms antes de que empiece a procesarse. Esos 50 ms ya se han gastado sin haber hecho nada útil.

Quedan 50 ms para procesar el evento y producir el fotograma con la respuesta visual. De ahí la guía: procesa el evento en 50 ms y así el total se mantiene por debajo de 100 aunque hayas tenido mala suerte con el encolado.

La estructura de este razonamiento reaparece constantemente en rendimiento web: el presupuesto que ves no es el que tienes, porque hay que descontar el peor caso de lo que ya estaba ocurriendo. Es exactamente la misma lógica que hay detrás del retraso de entrada del INP, que es precisamente el tiempo que el evento pasó esperando a que el hilo se liberase.

RAIL frente a Core Web Vitals

La correspondencia no es de uno a uno, y las diferencias son informativas.

Contexto RAIL Métrica moderna equivalente Diferencia relevante
Respuesta INP El INP mide toda la interacción hasta el siguiente pintado, no solo el procesamiento del evento; y toma la peor interacción de la página, no una cualquiera
Animación Ninguna Core Web Vital Sigue sin haber métrica oficial de fluidez; se mide con perfiles y con métricas propias
Tiempo ocioso Ninguna directa; TBT en laboratorio El TBT resume cuánto tiempo el hilo estuvo bloqueado, que es el complemento del tiempo ocioso
Carga LCP, con TTFB y FCP como diagnóstico El LCP mide el elemento principal visible, no un estado abstracto de interactividad

La casilla vacía llama la atención: no existe una Core Web Vital de fluidez de animación. Es una laguna reconocida y una de las razones por las que “las tres en verde” no equivale a “el sitio va bien”. Si tu producto vive de interacciones continuas, mapas, editores, listas con scroll pesado, tendrás que definir métricas propias, y RAIL te da el presupuesto de partida.

El presupuesto de tiempo ocioso en la práctica

De los cuatro contextos, el de tiempo ocioso es el que más cambia la forma de escribir código, porque implica una disciplina concreta: todo trabajo que no sea necesario para la interacción actual se difiere, y se difiere troceado.

El patrón mínimo es este:

// Trabajo diferido que respeta el presupuesto de 50 ms por trozo.
function procesarEnTrozos(items, trabajo) {
  let i = 0;

  function siguienteTrozo(deadline) {
    // timeRemaining() decrece conforme consumes el hueco ocioso.
    while (i < items.length && deadline.timeRemaining() > 1) {
      trabajo(items[i]);
      i++;
    }
    if (i < items.length) {
      requestIdleCallback(siguienteTrozo);
    }
  }

  requestIdleCallback(siguienteTrozo);
}

requestIdleCallback recibe un objeto con timeRemaining(), que devuelve los milisegundos que quedan del hueco ocioso actual. El bucle se detiene cuando se agota y reprograma el resto. El navegador limita ese hueco a un máximo del orden de 50 ms precisamente por la razón de RAIL: para que una entrada del usuario nunca espere más de eso.

Hay dos advertencias que ahorran disgustos. La primera es que requestIdleCallback puede tardar mucho en dispararse, o no dispararse nunca, si la página está permanentemente ocupada; acepta un timeout en las opciones para forzar la ejecución. La segunda es que su soporte llegó tarde a Safari, así que conviene comprobar su existencia antes de usarlo y caer a otro mecanismo si no está.

El presupuesto de animación se rompe casi siempre por una lectura, no por una escritura

Cuando alguien tiene una animación a tirones, el instinto es buscar código pesado dentro del bucle. Nueve de cada diez veces el culpable es de otra clase: una única lectura de una propiedad geométrica del DOM, un offsetHeight o un getBoundingClientRect, colocada después de haber escrito algo. Esa lectura obliga al navegador a resolver el layout pendiente en ese mismo instante, de forma síncrona, y convierte una operación que costaba microsegundos en una que cuesta milisegundos, multiplicada por el número de elementos del bucle. Lo insidioso es que el código parece inocente y que el coste no está donde lo lees: está en la escritura anterior que dejaste pendiente. La señal en un perfil es inconfundible, un bloque de recálculo de estilo o de layout justo debajo de tu función, y una vez que aprendes a reconocerla la encuentras en todas partes. La regla que la evita cabe en una línea: agrupa todas las lecturas geométricas antes de la primera escritura, nunca las intercales.