Aggiungi effetti di entrata e uscita agli elementi quando entrano o escono dallo scrollport

Gli effetti di entrata e uscita sono animazioni che vengono attivate quando un elemento entra o esce dall'area visibile. Questo può essere utilizzato per creare esperienze utente coinvolgenti e dinamiche. Ad esempio, puoi utilizzare un effetto di entrata per applicare la dissolvenza in entrata a un elemento quando scorre nella visualizzazione o un effetto di uscita per ridurlo quando scorre fuori dalla visualizzazione.

Come implementare

Per aggiungere effetti di entrata e uscita a un elemento, devi combinare alcune proprietà CSS. Di seguito sono fornite istruzioni dettagliate:

  1. Crea @keyframes separati per le animazioni di entrata e uscita. L'animazione di entrata verrà applicata quando l'elemento entra nell'area visibile, mentre l'animazione di uscita verrà applicata quando l'elemento esce.

    @keyframes slide-in {
      from { transform: translateX(-100%); }
    }
    @keyframes slide-out {
      to { transform: translateX(100%); }
    }
    
  2. Associa i fotogrammi chiave di entrata e di uscita all'elemento. Puoi farlo definendo più animazioni nella proprietà animation.

    • Assegna all'animazione di entrata un valore animation-fill-mode di backwards in modo che applichi lo stato iniziale prima dell'inizio dell'animazione.
    • Assegna all'animazione di uscita un valore animation-fill-mode di forwards in modo che mantenga lo stato finale al termine dell'animazione.
    .animated-element {
      animation:
        slide-in 1s linear backwards,
        slide-out 1s linear forwards;
    }
    
  3. Crea una sequenza temporale di visualizzazione e collegala alle animazioni. Una View Timeline è un tipo di sequenza temporale collegata alla visibilità di un elemento nell'area visibile. Puoi crearne una utilizzando la funzione view() e poi applicarla alle tue animazioni utilizzando la proprietà animation-timeline.

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

    Per impostazione predefinita, view() monitora l'elemento sull'asse block. Se devi monitorarlo sull'asse inline, puoi utilizzare view(inline).

  4. Limita le animazioni agli intervalli entry e exit. La proprietà animation-range consente di specificare in quale parte della sequenza temporale deve essere eseguita un'animazione.

    • L'intervallo entry copre il tempo che intercorre tra il momento in cui l'elemento entra per la prima volta nell'area visibile e quello in cui è completamente visibile.
    • L'intervallo exit copre il periodo di tempo che va dal momento in cui l'elemento inizia a uscire dall'area visibile fino a quando non viene completamente nascosto.
    .animated-element {
      animation-range: entry, exit;
    }
    

Esempio di codice

Questo codice anima i figli diretti dello scroller durante lo scorrimento utilizzando una cronologia di visualizzazione anonima:

@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;
    }
  }
}

Quando gli elementi entrano nello scrollport, viene riprodotta l'animazione grow, mentre quando escono dallo scrollport, viene riprodotta l'animazione shrink.

Il seguente codice ha lo stesso risultato visivo, ma anima gli elementi secondari diretti dello scroller durante lo scorrimento utilizzando una view-timeline con nome:

@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;
    }
  }
}

Best practice

Quando utilizzi le animazioni di scorrimento, è importante seguire alcune best practice per garantire un'esperienza fluida e accessibile:

  • SÌ: includi il rilevamento delle funzionalità. Non tutti i browser supportano le animazioni basate sullo scorrimento. Utilizza @supports ((animation-timeline: view()) and (animation-range: entry)) per verificare il supporto e fornire un'alternativa per i browser che non lo supportano.
    • Il controllo (animation-range: entry) DEVE essere incluso qui per filtrare i browser con supporto solo parziale.
    • NON utilizzare il pacchetto scroll-timeline-polyfill per la strategia di riserva, in quanto non è completo di funzionalità e presenta molti problemi noti.
    • Se l'animazione è considerata solo decorativa, opta per il potenziamento progressivo e NON fornire un fallback.
  • SÌ: rispetta le preferenze degli utenti. Alcuni utenti preferiscono un movimento ridotto sul web. Utilizza la query supporti prefers-reduced-motion per disattivare o ridurre le animazioni per questi utenti.
  • SÌ: prova ad animare solo le proprietà CSS con prestazioni elevate. Per ottenere animazioni più fluide, limita l'animazione alle proprietà che possono essere gestite dal thread del compositore del browser, come transform e opacity. L'animazione di altre proprietà come width o height può causare problemi di prestazioni.
  • SÌ: utilizza l'ordine di dichiarazione corretto. Quando utilizzi la proprietà abbreviata animation, dichiara animation-timeline dopo per evitare che la proprietà abbreviata reimposti la sequenza temporale.

Quando utilizzi la funzione view() per creare un'animazione basata sullo scorrimento:

  • FACOLTATIVO Specifica l'asse da monitorare: se non scegli come target l'asse block predefinito (ad esempio in uno scroller orizzontale), specifica l'asse da monitorare con view(block) o view(inline).
  • Quando l'animazione non viene applicata al soggetto monitorato, utilizza una sequenza temporale di visualizzazione con nome.

Quando utilizzi la proprietà view-timeline per creare un'animazione basata sullo scorrimento:

  • SÌ: utilizza un CSS <dashed-ident> per il nome.
  • FACOLTATIVO Specifica l'asse da monitorare: se non scegli come target l'asse block predefinito (ad esempio in uno scroller orizzontale), specifica l'asse da monitorare con view-timeline-axis.
  • SÌ: assicurati che l'ambito della ricerca funzioni. Quando l'elemento che dichiara view-timeline non è un antenato dell'albero piatto dell'elemento animato, aumenta la visibilità del nome di view-timeline utilizzando timeline-scope su un antenato condiviso.

Preferisci un view-timeline con nome quando è necessario animare più elementi o elementi secondari del soggetto monitorato.

Supporto del browser e strategie di riserva

Pertanto, in genere è necessaria una strategia di riserva.

Per i browser che non supportano le animazioni basate sullo scorrimento, puoi utilizzare un comportamento di riserva per ricreare gli effetti visivi. I fallback vengono in genere creati con un listener di scorrimento (per gli effetti ScrollTimeline) o con l'API IntersectionObserver (per gli effetti ViewTimeline).

Nei browser con supporto integrato per le animazioni basate sullo scorrimento, utilizza SEMPRE l'implementazione CSS nativa, in quanto è più efficiente.

Tieni presente che non tutti gli effetti possono essere ricreati utilizzando l'approccio dei fallback.

Per questo caso d'uso specifico, lo script seguente applica il comportamento di riserva per i browser che non supportano le animazioni basate sullo scorrimento. Utilizza un IntersectionObserver per monitorare la visibilità dell'elemento .wrapper e aggiorna la proprietà transform dei livelli in base alla posizione di scorrimento.

<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>