wandres.dev
SCROLL-DRIVEN III · view() y los rangos

El modelo de los rangos: cover, contain, entry y exit

Los seis rangos nombrados de una view timeline definidos con precisión geométrica, incluido el caso degenerado del sujeto más alto que el scrollport.

⏱ 22 min

Esta es la lección que hay que leer dos veces. Los seis rangos nombrados de una view timeline no son azúcar sintáctico sobre porcentajes: son seis formas distintas de responder a la pregunta “¿en qué instante consideramos que el elemento está entrando?”, y cada una da una respuesta distinta en cuanto el elemento es más alto que la pantalla. Casi todos los efectos de scroll que se sienten mal están construidos sobre el rango equivocado.

🎯 Al terminar esta lección sabrás
  • Definir con precisión los seis rangos en términos de bordes y posiciones.
  • Predecir el comportamiento de cada rango cuando el sujeto es mayor que el scrollport.
  • Elegir entre entry y entry-crossing con un criterio geométrico.
  • Traducir un requisito de diseño en un rango concreto sin ensayo y error.

Las cuatro posiciones clave

Todo el modelo se construye sobre cuatro instantes del recorrido de un elemento por delante del scrollport. Fijamos el vocabulario: el sujeto tiene un borde de inicio y un borde final en el eje de la timeline; el rango de visibilidad tiene los suyos. En escritura horizontal de arriba abajo y eje de bloque, “inicio” es arriba y “final” es abajo.

Scrolleando hacia abajo, un elemento que viene desde debajo de la pantalla atraviesa estas cuatro posiciones en este orden:

flowchart TB
A[P1 el borde de inicio del sujeto coincide con el borde final del rango de visibilidad] --> B[P2 el sujeto acaba de entrar entero]
B --> C[P3 el sujeto empieza a salir]
C --> D[P4 el borde final del sujeto coincide con el borde de inicio del rango de visibilidad]
A -.-> H[cover empieza en P1 y acaba en P4]
A -.-> E[entry empieza en P1 y acaba en P2]
B -.-> F[contain empieza en P2 y acaba en P3]
C -.-> G[exit empieza en P3 y acaba en P4]
style A fill:#89b4fa,color:#11111b
style B fill:#89b4fa,color:#11111b
style C fill:#89b4fa,color:#11111b
style D fill:#89b4fa,color:#11111b
style H fill:#94e2d5,color:#11111b
style E fill:#a6e3a1,color:#11111b
style F fill:#cba6f7,color:#11111b
style G fill:#f9e2af,color:#11111b

De ahí salen los cuatro rangos principales sin ninguna ambigüedad:

cover es el rango completo de la timeline: de P1 a P4. Cubre todo el intervalo durante el cual alguna parte del sujeto está dentro del rango de visibilidad. Es el rango por defecto: si no escribes animation-range, tu animación va de cover 0% a cover 100%.

entry va de P1 a P2: el elemento está entrando. Su 0% coincide con el 0% de cover y su 100% coincide con el 0% de contain.

contain va de P2 a P3: el elemento está completamente dentro del rango de visibilidad.

exit va de P3 a P4: el elemento está saliendo. Su 0% coincide con el 100% de contain y su 100% con el 100% de cover.

Esa cadena de equivalencias —entry acaba donde contain empieza, contain acaba donde exit empieza— no es una coincidencia de diseño: es la definición literal de la especificación, y significa que los tres rangos teselan cover sin huecos ni solapes.

El caso que rompe la intuición

Todo lo anterior asume que el sujeto cabe dentro del rango de visibilidad. Cuando no cabe —una sección a pantalla completa, una imagen de fondo enorme, un bloque de texto largo— la definición de contain cambia de sentido, y ahí es donde la mayoría de la gente descubre que su efecto se comporta al revés en móvil.

La especificación define contain como “el rango durante el cual la caja principal está completamente contenida por, o cubre completamente, su rango de visibilidad”. Las dos mitades de esa frase describen situaciones geométricamente opuestas:

  • Si el sujeto es más pequeño que el rango de visibilidad, contain es el tramo en que el sujeto está entero dentro. Empieza cuando su borde final entra y acaba cuando su borde de inicio va a salir.
  • Si el sujeto es más grande, contain es el tramo en que el sujeto tapa por completo el rango de visibilidad. Empieza cuando su borde de inicio llega arriba del todo y acaba cuando su borde final llega abajo del todo.

Y en el punto exacto en que las dos alturas coinciden, contain tiene duración cero: es un instante, no un intervalo.

La consecuencia práctica es dura de aceptar la primera vez. Un efecto ajustado con animation-range: contain en un monitor de escritorio, con tarjetas que caben holgadamente, puede tener duración cero o casi cero en un móvil donde la misma tarjeta ocupa más que la pantalla. No falla ruidosamente: la animación salta del primer keyframe al último en un píxel de scroll. El síntoma que reporta el usuario es “en el móvil se ve un parpadeo”.

Relación de tamaños entry contain exit
Sujeto más pequeño que el scrollport Dura lo que mide el sujeto Dura la diferencia entre los dos Dura lo que mide el sujeto
Sujeto igual que el scrollport Dura lo que mide el sujeto Duración cero Dura lo que mide el sujeto
Sujeto más grande que el scrollport Dura lo que mide el scrollport Dura la diferencia entre los dos Dura lo que mide el scrollport

Fíjate en lo que dice la tabla: cuando el sujeto es más grande, la duración de entry deja de depender del sujeto y pasa a depender del scrollport. El rango cambia de qué depende, no solo de cuánto mide.

Los dos rangos de cruce

Quedan dos rangos, y son los que casi nadie usa porque casi nadie entiende para qué están. entry-crossing y exit-crossing responden a una pregunta distinta: no “cuándo está el elemento entrando”, sino “cuándo está el elemento cruzando un borde concreto”.

entry-crossing es el rango durante el cual la caja del sujeto cruza el borde final del rango de visibilidad. Su 0% es la última posición en que el borde de inicio del sujeto coincide con ese borde final —el momento en que el sujeto empieza a asomar— y su 100% es la primera posición en que el borde final del sujeto coincide con ese mismo borde. Dicho de otro modo: desde que asoma la primera fila de píxeles hasta que ha pasado la última, medido siempre contra el mismo borde de la pantalla.

exit-crossing es lo mismo contra el borde de inicio del rango de visibilidad: desde que el sujeto empieza a salir por arriba hasta que ha salido del todo por arriba.

La diferencia con entry es exactamente esta: entry mide contra el elemento y el scrollport a la vez; entry-crossing mide solo contra un borde. Y por eso entry-crossing no cambia de naturaleza cuando el sujeto es mayor que la pantalla: su duración es siempre la altura del sujeto, punto. Es el rango que quieres cuando el efecto tiene que sentirse igual independientemente del tamaño de la ventana.

Con un sujeto más pequeño que el scrollport, entry y entry-crossing coinciden. En cuanto el sujeto crece, se separan, y es entonces cuando la elección importa.

Los porcentajes del rango y los porcentajes de los keyframes son dos escalas distintas

Este es el error conceptual que cuesta más caro y el que produce ese “casi funciona pero va desfasado” que nadie sabe arreglar. Cuando escribes animation-range: entry 25% entry 75%, no estás diciendo “reproduce el tramo del 25 al 75 por ciento de mi animación”. Estás diciendo otra cosa completamente distinta: el rango activo de la animación es ese tramo de la timeline, y dentro de ese tramo la animación se reproduce entera, del 0 al 100 por ciento de sus keyframes. Los dos sistemas de porcentajes son independientes y se componen: el animation-range recorta un trozo de la timeline, y los keyframes se reescalan para llenar ese trozo. Un keyframe al 50% de tu @keyframes cae en el punto medio del rango que hayas definido, no en el 50% de la timeline. La prueba de que lo has entendido es saber responder a esta pregunta: si tienes animation-range: entry y un keyframe en el 50%, ¿en qué instante físico ocurre ese keyframe? Respuesta: cuando el elemento lleva la mitad de su entrada, no cuando lleva la mitad de su recorrido por la pantalla. Y el corolario que evita horas de ajuste a ciegas: si tu animación se siente descoordinada, casi nunca es culpa de los keyframes, es que el rango no es el que creías. Cambia el rango antes de tocar los porcentajes; tocar los porcentajes para compensar un rango mal elegido produce un CSS que funciona en tu ventana y en ninguna otra.

Del requisito al rango

La forma de trabajar que evita el ensayo y error es traducir la frase del diseño a una condición geométrica y de ahí al rango. Estas son las traducciones que cubren casi todo:

Lo que pide el diseño Rango
“Que aparezca al entrar en pantalla” entry
“Que aparezca cuando ya se ve entera” entry 100% como inicio
“Que se mueva mientras está en pantalla” contain, y solo si el sujeto es menor que el viewport
“Que se mueva durante todo su paso” cover, el valor por defecto
“Que se desvanezca al salir” exit
“Que reaccione al cruzar el borde inferior, sea del tamaño que sea” entry-crossing
“Que empiece un poco antes de asomar” cover con view-timeline-inset negativo

Ese último caso es la puerta de entrada a la siguiente pieza del sistema: el rango de visibilidad no tiene por qué ser el scrollport entero, y recortarlo o ampliarlo cambia dónde caen las cuatro posiciones clave sin tocar ningún rango. Es el contenido de la lección sobre view-timeline-inset.

⚔️ Mide los rangos con los ojos

Construye una página con tres bloques del mismo color y tres alturas: 20vh, 100vh y 180vh. Aplica a los tres la misma animación de background-color con animation-range: contain y scrollea despacio. Anota en qué tramo cambia el color de cada uno. Repite con entry y con entry-crossing. El objetivo no es que salga bonito: es que puedas predecir el resultado del tercer bloque antes de ejecutarlo.