Agrega efectos de entrada y salida a los elementos a medida que ingresan o salen del puerto de desplazamiento

Los efectos de entrada y salida son animaciones que se activan cuando un elemento entra o sale del viewport. Esto se puede usar para crear experiencias del usuario atractivas y dinámicas. Por ejemplo, puedes usar un efecto de entrada para que un elemento aparezca gradualmente a medida que se desplaza hacia la vista, o un efecto de salida para reducirlo a medida que se desplaza fuera de la vista.

Cómo implementarlo

Para agregar efectos de entrada y salida a un elemento, debes combinar algunas propiedades de CSS. A continuación, le presentamos una guía paso a paso:

  1. Crea un @keyframes independiente para las animaciones de entrada y salida. La animación de entrada se aplicará cuando el elemento ingrese al viewport, y la de salida, cuando salga.

    @keyframes slide-in {
      from { transform: translateX(-100%); }
    }
    @keyframes slide-out {
      to { transform: translateX(100%); }
    }
    
  2. Adjunta los fotogramas clave de entrada y salida al elemento. Para ello, define varias animaciones en la propiedad animation.

    • Asigna a la animación de entrada un animation-fill-mode de backwards para que aplique su estado inicial antes de que comience la animación.
    • Asigna a la animación de salida un animation-fill-mode de forwards para que mantenga su estado final una vez que se complete la animación.
    .animated-element {
      animation:
        slide-in 1s linear backwards,
        slide-out 1s linear forwards;
    }
    
  3. Crea un cronograma de vista y vincúlalo a las animaciones. Una línea de tiempo de vista es un tipo de línea de tiempo que se vincula a la visibilidad de un elemento en el viewport. Puedes crear uno con la función view() y, luego, aplicarlo a tus animaciones con la propiedad animation-timeline.

    .animated-element {
      animation-timeline: view();
    }
    

    De forma predeterminada, view() hace un seguimiento del elemento en el eje block. Si necesitas hacer un seguimiento en el eje inline, puedes usar view(inline).

  4. Limita las animaciones a los rangos entry y exit. La propiedad animation-range te permite especificar en qué parte de la línea de tiempo se debe ejecutar una animación.

    • El rango entry abarca el tiempo desde que el elemento ingresa por primera vez al viewport hasta que es completamente visible.
    • El rango exit abarca el tiempo desde que el elemento comienza a salir del viewport hasta que se oculta por completo.
    .animated-element {
      animation-range: entry, exit;
    }
    

Ejemplo de código

Este código anima los elementos secundarios directos del desplazador cuando se desplaza con una línea de tiempo de vista anónima:

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @keyframes grow {
      from {
        scale: 0.5;
      }
    }
    @keyframes shrink {
      to {
        scale: 0.5;
      }
    }

    .scroller > * {
      animation:
        grow auto linear backwards,
        shrink auto linear forwards;
      animation-timeline: view(inline);
      animation-range: entry, exit;
    }
  }
}

A medida que los elementos ingresan al puerto de desplazamiento, se reproduce la animación grow y, cuando salen de él, se reproduce la animación shrink.

El siguiente código tiene el mismo resultado visual, pero anima los elementos secundarios directos del desplazador en el desplazamiento con un view-timeline con nombre:

@media (prefers-reduced-motion: no-preference) {
  @supports ((animation-timeline: view()) and (animation-range: entry)) {
    @keyframes grow {
      from {
        scale: 0.5;
      }
    }
    @keyframes shrink {
      to {
        scale: 0.5;
      }
    }

    .scroller > * {
      view-timeline: --tl inline;
      animation:
        grow auto linear backwards,
        shrink auto linear forwards;
      animation-timeline: --tl;
      animation-range: entry, exit;
    }
  }
}

Prácticas recomendadas

Cuando uses animaciones controladas por el desplazamiento, es importante que sigas algunas prácticas recomendadas para garantizar una experiencia fluida y accesible:

  • SÍ incluye la detección de funciones: No todos los navegadores admiten animaciones controladas por el desplazamiento. Usa @supports ((animation-timeline: view()) and (animation-range: entry)) para verificar la compatibilidad y proporcionar un resguardo para los navegadores que no lo admiten.
    • La verificación de (animation-range: entry) DEBE incluirse aquí para filtrar los navegadores que solo tienen compatibilidad parcial.
    • NO uses el paquete scroll-timeline-polyfill para la estrategia de resguardo, ya que no tiene todas las funciones y tiene muchos problemas conocidos.
    • Si la animación solo se considera decorativa, opta por la mejora progresiva y NO proporciones un resguardo.
  • SÍ respeta las preferencias del usuario: Algunos usuarios prefieren tener menos movimiento en la Web. Usa la consulta de medios prefers-reduced-motion para inhabilitar o reducir las animaciones para estos usuarios.
  • SÍ intenta animar solo las propiedades de CSS que tengan un buen rendimiento: Para obtener las animaciones más fluidas, limítate a animar las propiedades que pueda controlar el subproceso del compositor del navegador, como transform y opacity. Animar otras propiedades, como width o height, puede provocar problemas de rendimiento.
  • SÍ uses el orden de declaración correcto: Cuando uses la propiedad abreviada animation, declara animation-timeline después de ella para evitar que la abreviatura restablezca la línea de tiempo.

Cuando uses la función view() para crear una animación controlada por el desplazamiento, ten en cuenta lo siguiente:

  • OPCIONAL Sé explícito sobre el eje que se debe seguir: Cuando no se oriente al eje block predeterminado (como en un desplazador horizontal), sé explícito sobre qué eje se debe seguir con view(block) o view(inline).
  • Cuando la animación no se aplique al sujeto seguido, usa un cronograma de vista con nombre.

Cuando uses la propiedad view-timeline para crear una animación controlada por el desplazamiento, ten en cuenta lo siguiente:

  • SÍ uses un <dashed-ident> de CSS para el nombre.
  • OPCIONAL Sé explícito sobre el eje que se debe seguir: Cuando no se oriente al eje block predeterminado (como en un desplazador horizontal), sé explícito sobre qué eje se debe seguir con view-timeline-axis.
  • SÍ asegúrate de que el alcance de la búsqueda funcione: Cuando el elemento que declara el view-timeline no es un ancestro de árbol plano del elemento animado, eleva la visibilidad del nombre del view-timeline con timeline-scope en un ancestro compartido.

Se prefiere un view-timeline con nombre cuando se deben animar varios elementos o elementos secundarios del sujeto al que se hace un seguimiento.

Compatibilidad con navegadores y estrategias de resguardo

Por lo tanto, suele ser necesaria una estrategia de resguardo.

En el caso de los navegadores que no admiten animaciones basadas en el desplazamiento, puedes usar un resguardo para recrear los efectos visuales. Por lo general, las alternativas se crean con un objeto de escucha de desplazamiento (para los efectos de ScrollTimeline) o la API de IntersectionObserver (para los efectos de ViewTimeline).

En los navegadores con compatibilidad integrada para animaciones basadas en el desplazamiento, usa SIEMPRE la implementación nativa de CSS, ya que tiene un mejor rendimiento.

Ten en cuenta que no todos los efectos se pueden recrear con el enfoque de resguardos.

Específicamente para este caso de uso, la siguiente secuencia de comandos aplica el resguardo para los navegadores que no admiten animaciones controladas por el desplazamiento. Usa un IntersectionObserver para hacer un seguimiento de la visibilidad del elemento .wrapper y actualiza la propiedad transform de las capas según la posición de desplazamiento.

<script>
  if (!CSS.supports('(animation-timeline: view()) and (animation-range: entry)')) {
    const observer = new IntersectionObserver(
      (entries) => {
        for (const entry of entries) {
          // This matches the effect as defined in the CSS example above.
          // Customize this further if needed.
          entry.target.style.scale = 0.5 + entry.intersectionRatio * 0.5;
        }
      },
      {
        threshold: Array.from({ length: 101 }, (_, i) => i / 100),
      }
    );

    document.querySelectorAll('.scroller > *').forEach((el) => {
      observer.observe(el);
    });
  }
</script>