Transizioni nello stesso documento

Funzionalità web: Transizioni di visualizzazione

Il problema

I siti web spesso forniscono più visualizzazioni di un oggetto, ad esempio un elenco di prodotti e poi una pagina dei dettagli per ogni prodotto. La navigazione tra le due visualizzazioni spesso sembra scollegata. Quando un utente fa clic sulla miniatura di un prodotto per visualizzarne i dettagli, la miniatura scompare e una nuova immagine più grande viene visualizzata immediatamente in un'altra parte dello schermo. Questa mancanza di continuità rende più difficile per gli utenti monitorare le relazioni tra gli elementi.

La soluzione

L'API View Transitions ti consente di specificare coppie di elementi che esistono in stati diversi prima e dopo una transizione. Quando si attiva una transizione con document.startViewTransition() in una navigazione in una singola pagina (SPA), il browser identifica questi elementi condivisi in base al loro view-transition-name univoco condiviso. Quindi, calcola automaticamente la differenza di posizione, dimensioni e stile e li anima in modo fluido dallo stato precedente a quello nuovo. Questa transizione avviene nel livello superiore, anche sopra gli elementi con valori z-index elevati.

Guida all'implementazione

Passaggio 1: aggrega le modifiche di stato in startViewTransition

Per le applicazioni a pagina singola (SPA) o le semplici modifiche di stato, inserisci la logica che aggiorna il DOM in document.startViewTransition. Il browser acquisisce uno snapshot dello stato attuale, esegue l'aggiornamento e poi acquisisce il nuovo stato.

function navigate(view) {
  // MANDATORY: Wrap the update in startViewTransition
  document.startViewTransition(() => updateDOM(view));
}

Passaggio 2: assegna i nomi delle transizioni condivise

Utilizza la proprietà CSS view-transition-name per indicare al browser quali elementi devono essere trasformati. Il nome può essere qualsiasi (tranne none). OBBLIGATORIO: non deve esserci più di un elemento prima e dopo con un determinato view-transition-name. Se sono presenti due o più elementi con un determinato view-transition-name, il DOM verrà aggiornato immediatamente al nuovo stato, senza una transizione.

Puoi utilizzare più view-transition-name per trasformare più coppie di elementi. Ad esempio, potresti voler applicare transizioni separate all'immagine e al titolo del prodotto.

Poiché nella visualizzazione elenco sono presenti più elementi, non puoi assegnare a tutti lo stesso view-transition-name. Questo problema può essere risolto in due modi in una SPA.

  1. Pagina dei dettagli dinamica: assegna a ogni elemento della pagina dell'elenco un view-transition-name univoco, quindi applica dinamicamente questo nome all'elemento corrispondente nella pagina dei dettagli quando l'elemento dell'elenco viene selezionato, come mostrato qui.

    /* In the list view, give each */
    #product-1 { view-transition-name: p1 }
    #product-2 { view-transition-name: p2 }
    #product-3 { view-transition-name: p3 }
    
    function updateDOM(clickedTransitionName){
      const hero = document.getElementById("hero");
      hero.style.viewTransitionName = clickedTransitionName;
    }
    
  2. Elemento dell'elenco dinamico: assegna all'elemento nella pagina dei dettagli un view-transition-name e applica questo nome all'elemento nella pagina dell'elenco quando viene selezionato. Rimuovi view-transition-name dall'elemento nella pagina dell'elenco quando torni alla pagina dell'elenco.

L'elemento #hero nella pagina dei dettagli e l'elemento .thumbnail selezionato nella pagina dell'elenco condividono un view-transition-name.

#hero{
  view-transition-name: hero;
}
.thumbnail.selected {
  view-transition-name: hero;
}

Quando si fa clic su una miniatura, dobbiamo preparare la visualizzazione elenco assegnando view-transition-name utilizzando il selettore di classe .selected e apportando eventuali modifiche al DOM prima di avviare la transizione.

Poi, puoi chiamare document.startViewTransition() e applicare le modifiche per passare dalla visualizzazione dettagliata alla visualizzazione elenco della pagina.

Dopo essere tornato alla visualizzazione elenco, devi ripulire le classi di transizione della visualizzazione per evitare che la navigazione successiva generi un errore. Puoi eseguire questa pulizia dopo che la promessa finished della transizione è stata risolta.

// Function called when a thumbnail is clicked
function goFromListToDetail(e){
  e.currentTarget.classList.add("selected");
  const hero = document.getElementById("hero");
  const bgColor = getComputedStyle(e.currentTarget).backgroundColor;
  hero.style.background = bgColor;

  // Trigger the transition, checking for support
  if (!document.startViewTransition) {
    document.body.classList.add("detail");
    // MANDATORY Accessibility Routing: Route focus to the newly revealed heading to announce context and preserve logical tab flow
    document.getElementById("detail-heading")?.focus();
    return; // MANDATORY: End function execution if view transitions are not supported.  
  }
  const transition = document.startViewTransition(() => {
    document.body.classList.add("detail");
  });
  // MANDATORY Accessibility Routing: Route focus after the view transition resolves
  transition.finished.finally(() => {
    document.getElementById("detail-heading")?.focus();
  });
}

// Function called when navigating from detail back to list view
function goFromDetailToList() {
  if (!document.startViewTransition) {
    document.body.classList.remove("detail");
    document.getElementById("list-heading")?.focus();
    return;
  }
  const transition = document.startViewTransition(() => {
    document.body.classList.remove("detail");
  });
  // Clean up the list view and route focus
  transition.finished.finally(() => {
    // Route focus back to list view
    document.getElementById("list-heading")?.focus();
    // Remove selected classList to remove view-transition-names
    document.querySelectorAll(".selected").forEach(
      (element) => {
        element.classList.remove("selected");
      },
    );
  });
}

Il metodo scelto dipenderà dal caso d'uso. L'elemento dell'elenco dinamico richiede meno CSS ripetuto, ma più pulizia manuale del codice JavaScript.

Passaggio 3: correggi l'allungamento delle proporzioni

Per impostazione predefinita, il browser applica una dissolvenza incrociata tra i vecchi e i nuovi snapshot all'interno di un gruppo che si estende per adattarsi a entrambi. Se stai eseguendo la transizione del testo, imposta la larghezza dell'elemento di testo su fit-content sia nella vecchia che nella nuova visualizzazione, in modo che le proporzioni dell'elemento di transizione siano stabili.

#list-page .title {
  width: fit-content;
}

#detail-page #title {
  width: fit-content;
}

Se stai eseguendo la transizione di elementi che cambiano le proporzioni, potrebbe essere necessario impostare l'altezza dei vecchi e dei nuovi pseudo-elementi sul 100% dello pseudo-elemento ::view-transition-pair().

::view-transition-old(hero),
::view-transition-new(hero){
  height: 100%;
}

Gli pseudo-elementi sono snapshot degli elementi attivi, quindi puoi anche utilizzare le dichiarazioni object-fit e object-position per un maggiore controllo dell'effetto di transizione.

Best practice

  • NON specificare troppe transizioni. Utilizza gli elementi condivisi solo per i contenuti principali che l'utente sta monitorando attivamente (ad es. immagini hero, intestazioni).
  • SÌ: rimuovi i valori view-transition-name temporanei al termine della transizione per evitare effetti collaterali sulle transizioni future.
  • NON eseguire la transizione di elementi con animazioni attive. Le transizioni di visualizzazione operano su istantanee, quindi le animazioni appariranno in pausa durante la transizione di visualizzazione.
  • SÌ: rispetta le preferenze degli utenti per il movimento ridotto utilizzando la query supporti prefers-reduced-motion.
  • MANDATORY Accessibility Routing: View transitions morph page layouts dynamically but do not manage programmatic focus. Se lo stato attivo rimane su un elemento nascosto o rimosso durante la transizione, lo stato attivo viene abbandonato, lasciando gli utenti di tastiere e tecnologie per la disabilità senza contesto. Sposta lo stato attivo in modo programmatico su un'intestazione di pagina o un contenitore di visualizzazione aggiornati (utilizzando tabindex="-1") immediatamente dopo gli aggiornamenti del DOM o quando la promessa finished della transizione di visualizzazione viene risolta.
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) {
    animation: none !important;
  }
}

Strategie di fallback

L'API View Transitions è progettata per il potenziamento progressivo. I browser che non la supportano eseguiranno semplicemente l'aggiornamento del DOM immediatamente senza animazione.

function navigate(){
  if (!document.startViewTransition) {
    // Fallback: Just update the DOM
    updateDOM();
  } else {
    document.startViewTransition(() => updateDOM());
  }
}