Transizioni Animate in MPA con la View Transitions API

Transizioni Animate in MPA con la View Transitions API

Scritto da Francesco Di Donato 13 luglio 2025 5 minuti di lettura

Navigando su questo sito, potresti notare che le animazioni non si limitano agli elementi di una singola pagina. Ad esempio, quando selezioni un articolo dalla lista, la sua immagine in miniatura si espande fluidamente per diventare l’hero image nella pagina di dettaglio.

Questo avviene nonostante il sito utilizzi un’architettura tradizionale Multi-Page Application (MPA), dove ogni navigazione comporta il caricamento di una nuova pagina.

Queste transizioni, come quella che vedi nell’esempio interattivo di carrello e-commerce qui sotto, sono ottenute tramite Progressive Enhancement utilizzando la View Transitions API. Il concetto di progressive enhancement è fondamentale: la funzionalità viene aggiunta come un livello migliorativo.

Un ulteriore vantaggio è che l’API rispetta nativamente le preferenze dell’utente. Se nel sistema operativo è attiva l’opzione per la riduzione del movimento (prefers-reduced-motion), le transizioni vengono disabilitate in automatico, garantendo accessibilità senza scrivere una riga di codice in più.

Carrello

Bottiglia di Skooma

Bottiglia di Skooma

Septim25.00

Spada Imperiale

Spada Imperiale

Septim23.00

Martello da Guerra Daedrico

Martello da Guerra Daedrico

Septim2500.00

Guanti Elfici

Guanti Elfici

Septim45.00

Elmo di Ferro

Elmo di Ferro

Septim60.00

Scudo della Guardia di Windhelm

Scudo della Guardia di Windhelm

Septim45.00

Armatura Orchesca

Armatura Orchesca

Septim400.00

L’interazione Client-Server: MPA vs. SPA

Per apprezzare l’importanza di questa API, è utile ripercorrere l’evoluzione delle architetture web.

Multi-Page Application (MPA)

Agli albori del web, il modello MPA era l’unico esistente. I browser erano client relativamente semplici, con motori JavaScript poco potenti. Il loro compito principale era renderizzare HTML e CSS inviati dal server.

Di conseguenza, l’intero stato dell’applicazione (chi sei, cosa hai nel carrello, etc.) doveva risiedere e essere gestito quasi esclusivamente sul server. Ad ogni interazione, il browser richiedeva una pagina completamente nuova.

sequenceDiagram participant Browser as Utente participant Server as Server Note over Browser: Utente clicca su un link o invia un form Browser->>Server: Richiesta HTTP per una nuova pagina (es. GET /prodotti) activate Server Note right of Server: Il server recupera lo stato<br/>(es. sessione utente, dati dal DB)<br/>e genera una pagina HTML completa. Server-->>Browser: Risposta HTTP 200 OK (invia intero file HTML) deactivate Server Note over Browser: Il contesto della pagina precedente<br/>viene completamente distrutto.<br/>Il browser renderizza la nuova pagina da zero.

Single-Page Application (SPA)

Con l’evoluzione dei browser e la crescente potenza di JavaScript, è emerso il modello SPA. In questa architettura, una porzione significativa dello stato applicativo viene delegata al client. La pagina HTML viene caricata una sola volta e le interazioni successive aggiornano dinamicamente la vista tramite JavaScript, creando un’esperienza utente fluida e reattiva.

Un’architettura SPA, dove la pagina di base è persistente, permette di manipolare liberamente le risorse allocate dal browser per quel tab. Animare un elemento da uno stato A a uno stato B è quindi un’operazione nativa e relativamente semplice.

sequenceDiagram participant App JS as JavaScript App participant Client as Browser participant Server %% --- Fase 1: Caricamento Iniziale --- Note over Client: Utente visita il sito per la prima volta Client->>Server: GET / (Richiesta HTML Shell) activate Server Server-->>Client: HTML Shell (struttura base) deactivate Server Client->>Server: GET /app.js (Richiesta Bundle JS) activate Server Server-->>Client: Bundle JavaScript completo deactivate Server activate App JS Note over App JS, Client: Boot (hijack routing) Note over App JS, Client: Render vista iniziale %% --- Fase 2: Navigazione Interna --- Note over App JS, Client: Utente clicca un link interno Note over App JS: Azione intercettata App JS->>Server: Chiamata API (GET /api/prodotti) activate Server Note over Server: Server recupera solo i dati necessari<br/>e risponde con JSON. Server-->>App JS: Dati in formato JSON deactivate Server Note over App JS: Usa il JSON per aggiornare<br/>dinamicamente il DOM della pagina. Note over App JS, Client: Nessun ricaricamento completo deactivate App JS

La Sfida delle Transizioni in una MPA

In una MPA, ogni volta che si naviga verso una nuova pagina, il contesto della pagina precedente viene completamente distrutto. Tutte le variabili, gli elementi del DOM e gli stati in memoria vengono eliminati per far posto al nuovo ambiente.

Questo rende impossibile usare JavaScript per animare un elemento tra le due pagine.

Anche se meccanismi come localStorage, sessionStorage e i cookie permettono di mantenere dati tra le navigazioni, non risolvono il problema dell’animazione. Essi persistono dati, non elementi del DOM durante il brevissimo istante della transizione tra il rendering di una pagina e l’altra. Questo è un limite intrinseco del modello di navigazione dei browser, una sandbox le cui regole non possiamo aggirare.

La Soluzione: View Transitions API

La View Transitions API nasce per superare esattamente questo limite. Introdotta in Chrome a partire dalla versione 111 (Marzo 2023) e decentemente supportata nella maggior parte dei browsers, permette di orchestrare transizioni animate anche tra documenti diversi in una MPA.

Con poche righe di CSS, è possibile istruire il browser a gestire la transizione di elementi specifici.

1. Attivare le transizioni tra pagine

Il primo passo è abilitare la funzionalità per le navigazioni cross-documento. Questo si fa aggiungendo una semplice regola nel CSS di entrambe le pagine (quella di partenza e quella di arrivo).

/* style.css */
@view-transition {
 navigation: auto;
}

Questa regola indica al browser di intercettare le navigazioni tra pagine della stessa origine e di applicare una transizione di default (una dissolvenza incrociata, un fade-in/fade-out).

2. Collegare gli Elementi

Il passaggio chiave è dire al browser quale elemento nella pagina A corrisponde a quale elemento nella pagina B. Questo si ottiene assegnando lo stesso, unico valore alla proprietà CSS view-transition-name a entrambi gli elementi.

Pagina di Listing (/blog):

<a href="/blog/mio-post">
 <img
  src="/path/to/thumbnail.jpg"
  style="view-transition-name: hero-image-post-123;"
 />
</a>

Pagina del Post (/blog/mio-post):

<img
 src="/path/to/hero-image.jpg"
 style="view-transition-name: hero-image-post-123;"
 class="hero-image"
/>

Assegnando lo stesso view-transition-name (hero-image-post-123), il browser capisce che questi due elementi sono concettualmente lo stesso e animerà la transizione tra le loro diverse dimensioni e posizioni, creando un effetto fluido e professionale che prima era un’esclusiva delle SPA.

Nota come il nome hero-image-post-123 sia specifico. In un’applicazione reale, questo valore non sarebbe statico ma generato dinamicamente, ad esempio utilizzando l’ID univoco del prodotto o dell’articolo (es. hero-image-post-${post.id}). Questo assicura che ogni elemento della lista punti correttamente e senza ambiguità alla sua controparte nella pagina di destinazione.

Attenzione: La proprietà view-transition-name deve essere unica nel DOM in un dato momento. Se due elementi visibili hanno lo stesso nome, il browser non saprà come gestire la transizione e l’animazione non avverrà.

La generazione dinamica basata su ID risolve proprio questo problema. Puoi infatti creare il valore di view-transition-name in modo dinamico:

<div style="view-transition-name: hero-image-post-${post.id};"> />

3. (Opzionale) Personalizzare l’Animazione

Di default, il browser applica una dissolvenza incrociata (cross-fade). Tuttavia, hai il pieno controllo sull’animazione tramite CSS. Utilizzando degli speciali pseudo-elementi, puoi definire animazioni complesse e su misura.

Ad esempio, per modificare l’animazione di default della pagina e farla scorrere, potresti usare:

::view-transition-old(root) {
  animation: slide-from-right 0.5s ease-out;
}
::view-transition-new(root) {
  animation: slide-to-left 0.5s ease-in;
}

/* E puoi animare in modo specifico l'elemento condiviso! */
::view-transition-new(hero-image-post-123) {
   /* L'animazione di transform (scala, posizione) 
      è gestita dal browser. Qui puoi aggiungere altro,
      come una transizione sul `border-radius`. */
   transition: border-radius 0.5s;
}

Questo apre infinite possibilità creative che vanno ben oltre l’effetto standard.