Animación de páginas web
Resumen
El movimiento en la web falla de dos maneras: no se ejecuta o se ejecuta mal. Ambas tienen causas específicas y localizables. Adivinar curvas de easing es lo último que se debe hacer, no lo primero.
Principio fundamental: antes de ajustar una animación, demuestra que se está ejecutando en los elementos en los que crees que se está ejecutando.
Para el diseño de escenas fijas y sincronizadas con scroll, consulta choreographing-scroll-stories. Esta habilidad se centra en la mecánica: hacer que el movimiento se ejecute, y que se ejecute bien.
Cuando una animación no se ejecuta en absoluto
Verifica en este orden. Los primeros tres cubren casi todo.
| Síntoma | Causa | Comprobación |
|---|---|---|
| Nada se mueve, elementos visibles | El selector coincide con cero elementos | document.querySelectorAll(sel).length en la consola |
| Nada se mueve, elementos invisibles | La revelación estableció opacity: 0 y el disparador nunca se activó | opacity calculada en el elemento |
| Un fundido cruzado nunca se funde | Ambas capas están en opacity: 1; solo existe la regla de "activado" | Debe haber una regla de estado de reposo que establezca la capa entrante en 0 |
| Se dispara inmediatamente al cargar | El disparador se evaluó antes de que el diseño se asentara | Ejecutar después de load, no de DOMContentLoaded, cuando las imágenes dimensionan la página |
| Se dispara una vez y nunca más | once: true más un elemento que vuelve a entrar | ¿Intencionado? |
| Nada se ejecuta en una pestaña oculta o panel sin cabeza | El ticker está pausado mientras document.hidden | Forzarlo: gsap.ticker.lagSmoothing(0); gsap.ticker.tick() |
| Los disparadores están desfasados unos cientos de píxeles | Las imágenes se cargaron después de medir los disparadores | ScrollTrigger.refresh() al cargar y al redimensionar (debounce de 200 ms) |
Vale la pena detallar el del fundido cruzado porque parece correcto en una revisión de código:
/* Esto solo no hace nada. */
.tile.is-swapped .layer.is-alt { opacity: 1 }
/* El estado de reposo es lo que lo convierte en una animación. */
.layer.is-alt { opacity: 0 }
.tile.is-swapped .layer.is-alt { opacity: 1 }
Presentaciones y movimiento ambiental
- El titular es visible rápidamente. Reduce una presentación hasta que el titular se muestre en aproximadamente 1,1 s. Una presentación de 3,8 s es una pantalla de carga.
- Las fotos cambian desvaneciendo una celda y apareciendo en otra. Nunca se deslizan. Cada foto es un elemento, nunca aparece dos veces en pantalla y nunca se desvanece mientras su copia aparece.
- El cambio ambiental es lento e irregular. Un mosaico cada 1,8 a 3,4 s se percibe como una cuadrícula viva mientras un solo mosaico cambia lentamente. No acortes el intervalo para que cada mosaico cambie más rápido.
- Barajar en rondas. Cada mosaico cambia una vez antes de que se repita alguno, y cada selección proviene de la mitad más lejana de los últimos tres.
- Pausa lo que nadie puede ver. Los bucles ambientales se detienen fuera de la pantalla y en una pestaña oculta, y se reanudan donde estaban.
- Las presentaciones no se repiten en cada regreso. Una vez que la presentación se asienta, solo se mueve la capa ambiental.
Revelados al desplazar
- Solo establece
opacity: 0en elementos que realmente están debajo del pliegue al cargar. Ocultar todo indiscriminadamente significa que cualquier elemento sobre el pliegue es invisible hasta un desplazamiento que quizás nunca ocurra. - Revelar en
top 88%, no en la intersección completa. Esperar a que el elemento esté completamente visible significa que se anima después de que el lector ya lo está mirando. - Escalonar entre hermanos, no dentro de un componente. 60-90 ms entre tarjetas, 0 dentro de una tarjeta.
- Agrupar con un observador por grupo en lugar de uno por elemento.
Video
El video de fondo con reproducción automática tiene un error universal: el video se reproduce fuera de la pantalla, un observador lo pausa, y para cuando el lector llega, el bucle ya lleva 15 segundos y el ritmo inicial se ha perdido.
Elimina el atributo autoplay por completo.
pause() y load() en la inicialización.
Solo en la primera intersección: currentTime = 0, luego play().
Sirve un archivo 1x por debajo de 992 px o con una relación de píxeles del dispositivo inferior a 1,5, y un fotograma de póster para la pintura antes de que el video se decodifique. El video pesado en la página de inicio también consume ancho de banda: audítalo.
Tartamudeo
El desplazamiento lento en una fotografía fija es la causa más común. Un movimiento Ken Burns de menos de aproximadamente 1 píxel por fotograma remuestrea la imagen en cada fotograma y se percibe como brillo, no como movimiento.
- Muévete lo suficiente como para que sea movimiento, o no te muevas en absoluto.
- Para una pausa en un video renderizado, copia el fotograma codificado byte por byte en lugar de recodificar un fotograma idéntico. Cualquier otra cosa se desvía.
- Verifica la costura del bucle. Si el fotograma 0 y el fotograma final difieren, el bucle salta visiblemente en cada ciclo. Mide la diferencia absoluta media entre ellos.
- No apliques suavizado doble. Una biblioteca de desplazamiento suave más un valor
scrubpesado se suma a una página que va por detrás de la rueda. Si hay una biblioteca de desplazamiento suave, usascrub: 0.2.
Adhesivo
position: sticky dentro de una cuadrícula CSS se resuelve contra el área de la cuadrícula, no contra todo el contenedor. Un elemento adhesivo que solo tiene la altura de su propio contenido no tiene recorrido y se suelta inmediatamente.
.grid { align-items: stretch } /* el área ahora tiene altura completa */
.col { position: static; height: 100% }
.inner { position: sticky; top: 10rem }
Degradaciones
Cada animación tiene tres degradaciones, documentadas en la especificación de movimiento y probadas:
- Movimiento reducido.
prefers-reduced-motion: reducey una consulta?staticrenderizan los estados finales apilados: nada fijado, nada oculto, cada escena en su estado final. También cubre cadatransitionde CSS que hayas agregado. Enumerar los selectores animados pero olvidar la transición en el contenedor deja un color que aún se desliza. - Sin script. Todo permanece visible. Se agrega una clase después de aproximadamente 3,5 s si el script nunca se inició, y el CSS revela todo lo que está debajo.
- Teléfonos. Las escenas fijadas se apilan. Menciónalo, pruébalo.
Verificación
No informes que una animación funciona basándote en el código. Confírmalo en la página:
- Los recuentos de selectores no son cero para cada grupo animado
- La
opacitycalculada en un elemento sobre el pliegue es 1 antes de cualquier desplazamiento - El
currentTimedel video es inferior a 0,5 s en el momento en que se vuelve visible por primera vez - La emulación de movimiento reducido deja la página completamente legible y estática
- Sin script deja todo visible
- Nada anima
width,height,topoleft; usatransformyopacity - Los bucles ambientales no realizan cambios de estilo mientras están fuera de la pantalla
Señales de alerta
- Ajustar la duración o el easing antes de confirmar que el selector coincide
- Una regla
.is-activesin una regla de estado de reposo correspondiente transition: all- Una animación agregada porque la sección "se sentía vacía"
- Una foto que se desliza
- La misma foto en pantalla dos veces