toggleActions: los cuatro cruces y las ocho respuestas
Qué significa cada una de las cuatro posiciones de toggleActions, las ocho palabras clave disponibles, las combinaciones que de verdad se usan, y once, toggleClass y preventOverlaps.
toggleActions: "play none none none" es el valor por defecto y probablemente la cadena más copiada de todo GSAP sin que nadie sepa qué significan sus cuatro palabras. La estructura es sencilla en cuanto la ves: hay exactamente cuatro maneras de cruzar un intervalo, y esa cadena dice qué hacer en cada una. Elegir bien las cuatro es la diferencia entre una página que se siente coherente al subir y bajar y una en la que las animaciones se disparan al revés, se acumulan o simplemente desaparecen.
- Enumerar los cuatro cruces posibles de un intervalo y en qué orden aparecen en la cadena.
- Elegir entre las ocho palabras clave según lo que quieras que ocurra al volver hacia atrás.
- Distinguir
toggleActionsdeonce, detoggleClassy de las llamadas de retorno. - Reconocer cuándo
toggleActionsno es la herramienta y hay que bajar a los callbacks.
Cuatro cruces, en este orden
Un intervalo tiene dos fronteras, y cada una se puede cruzar en dos sentidos. Eso da cuatro sucesos, y toggleActions los lista siempre en el mismo orden:
| Posición | Suceso | Nombre del callback equivalente |
|---|---|---|
| 1ª | Cruzas el start bajando |
onEnter |
| 2ª | Cruzas el end bajando |
onLeave |
| 3ª | Cruzas el end subiendo |
onEnterBack |
| 4ª | Cruzas el start subiendo |
onLeaveBack |
El orden no es alfabético ni casual: es el orden en que ocurren si haces scroll hasta abajo del todo y luego vuelves arriba. Entrando, saliendo, volviendo a entrar, volviendo a salir. Recordarlo así evita tener que consultar la tabla.
Cada posición admite una de estas ocho palabras: play, pause, resume, reset, restart, complete, reverse y none. Las diferencias importan y varias se confunden entre sí.
play continúa desde donde esté el cabezal hacia adelante. restart vuelve al principio y reproduce; no es lo mismo que play si la animación quedó a medias. resume reanuda en la dirección en que iba, que puede ser hacia atrás si venía de un reverse. pause congela. reset devuelve el cabezal al principio y pausa, dejando el elemento en su estado inicial. complete salta al final sin animar. reverse reproduce hacia atrás. none no hace nada.
Las combinaciones que se usan de verdad
Hay infinitas combinaciones y en la práctica se usan cuatro.
// 1) Una sola vez, la mas comun para revelados de contenido
toggleActions: "play none none none"
// 2) Simetrica: entra animando, sale desanimando. Ideal para elementos de UI
toggleActions: "play none none reverse"
// 3) Reproduce cada vez que aparece, siempre desde cero
toggleActions: "restart none none reset"
// 4) Reversible por los dos lados, para secciones que se cruzan en ambos sentidos
toggleActions: "play reverse play reverse"
La primera es el revelado clásico: el texto aparece y se queda. Es la que quieres para casi todo el contenido editorial, porque un párrafo que se desvanece al salir de pantalla y reaparece al volver es agotador de leer.
La segunda es la que quieres para elementos de interfaz: una barra de navegación que se encoge, un botón flotante que aparece, un fondo que cambia. Al subir deshace lo que hizo, que es lo que el usuario espera de un control.
La tercera reinicia cada vez. Se usa poco y casi siempre es un error: si el usuario sube y baja tres veces, ve la misma animación tres veces. Tiene sentido en una animación de datos que quieres que se pueda “volver a ver”, nunca en un revelado de texto.
La cuarta es la única que se comporta de forma completamente simétrica en las cuatro fronteras, y es la que necesitas cuando el intervalo es corto y el usuario puede quedarse oscilando en el borde.
reverse reproduce hacia atrás respetando la duración y el ease; reset salta al estado inicial de golpe. Con un scroll lento la diferencia es estética. Con un scroll rápido, reverse deja la animación corriendo hacia atrás durante medio segundo mientras el usuario ya está tres secciones más abajo, y si vuelve a entrar en ese medio segundo, el play de la primera posición reanuda desde donde estuviera el cabezal. Es el origen del bug de “a veces el elemento se queda a medias”. Si tu animación es corta y el intervalo estrecho, reset es más predecible.
once, toggleClass y los límites del sistema
once: true no es una abreviatura de "play none none none", aunque el efecto visible se parezca. Hace tres cosas: fija toggleActions a ese valor, hace que onEnter se dispare como mucho una vez, y mata la instancia en cuanto se alcanza el final, retirando las escuchas de scroll y dejándola disponible para el recolector de basura. En una página con doscientos revelados de una sola vez, esa diferencia se nota: al llegar al pie, las doscientas instancias han desaparecido en vez de seguir comprobando su intervalo en cada tick.
gsap.from(".parrafo", {
autoAlpha: 0,
y: 24,
duration: 0.5,
stagger: 0.08,
scrollTrigger: { trigger: ".articulo", start: "top 75%", once: true },
});
Ojo con un matiz: once no mata la animación asociada, solo la instancia. Si la animación estaba a medias en el momento de morir la instancia, sigue corriendo hasta acabar por su cuenta.
toggleClass vive en un plano paralelo y conviene no confundirlo. Añade y quita una clase cuando la instancia pasa de inactiva a activa y viceversa, y no obedece a toggleActions: no hay forma de decirle “pon la clase al entrar pero no la quites al salir”. Si necesitas ese control asimétrico, la herramienta son los callbacks.
// Forma corta: la clase va al propio trigger
toggleClass: "en-vista"
// Forma de objeto: la clase va a otros elementos
toggleClass: { targets: ".menu a", className: "resaltado" }
Y hay un ajuste que resuelve un problema concreto de secciones consecutivas: preventOverlaps. Cuando dos secciones adyacentes tienen animaciones de entrada y el usuario baja de golpe, la segunda arranca mientras la primera sigue corriendo y se ven las dos superpuestas. Con preventOverlaps: true, al ir a disparar una animación se fuerzan al final todas las anteriores basadas en scroll. Con una cadena arbitraria en lugar de true, el efecto se limita a las instancias que compartan esa misma cadena, lo que permite tener grupos independientes.
Su pareja natural es fastScrollEnd, que fuerza al final la animación actual si abandonas su zona por encima de cierta velocidad, 2500 píxeles por segundo por defecto. También acepta un número para fijar tu propio umbral.
ScrollTrigger.defaults({ preventOverlaps: true, fastScrollEnd: 3000 });
Detrás de esas cuatro palabras hay una pregunta que rara vez se formula en voz alta, y que determina cómo se siente una página entera: ¿el scroll hacia arriba deshace el tiempo o solo cambia el punto de vista? Las dos respuestas son defendibles y producen productos distintos. Si eliges "play none none none" estás afirmando que el scroll es un recorrido con memoria: lo que ya ocurrió, ocurrió, y volver atrás te enseña el mundo tal como lo dejaste. Si eliges "play reverse play reverse" estás afirmando que el scroll es una cámara que se mueve sobre una escena estática, y que el estado de cada elemento es una función pura de la posición de la barra. La segunda opción es matemáticamente más limpia —el estado no depende del historial, solo de la posición— y es la que hace que una página sea reproducible: dos usuarios en la misma posición ven exactamente lo mismo. La primera es cognitivamente más amable, porque el contenido que ya has leído no se te desmonta delante al releerlo. El error no está en elegir una u otra; el error está en mezclarlas sin criterio dentro de la misma página, que es lo que ocurre por defecto cuando cada animación se configura copiando la de al lado. Una página donde los titulares tienen memoria y los fondos no, donde unos elementos se rebobinan y otros se quedan, produce una sensación de inestabilidad que nadie sabe señalar pero que todo el mundo nota. La regla que salva es aburrida: decide la política una vez para todo el proyecto, ponla en ScrollTrigger.defaults(), y trata cada desviación como una excepción que hay que justificar.
- Monta una animación con marcadores y
toggleActions: "play pause resume reset"y recorre las cuatro fronteras despacio hasta identificar visualmente cada una. - Sustituye
"play none none none"poronce: trueen un revelado y comprueba conScrollTrigger.getAll().lengthcuántas instancias quedan vivas al llegar al pie. - Provoca el bug de la animación a medias: intervalo corto,
reverseen la cuarta posición, y scroll rápido oscilando en el borde. Arréglalo conreset. - Pon dos secciones consecutivas con entradas de 0,8 segundos y baja de golpe. Añade
preventOverlaps: truey compara. - Define una política de
toggleActionspara todo tu proyecto conScrollTrigger.defaults()y lista las excepciones que necesitas justificar.