back to top

Transizioni e animazioni in Vue.js

In questa lezione presenteremo una panoramica degli strumenti messi a disposizione da Vue per implementare transizioni e animazioni al fine di migliorare l’esperienza utente ed assicurare un uso più piacevole di un’applicazione. Così facendo possiamo semplificare la navigazione all’interno di un sito rendendo meno brusco ed improvviso il passaggio fra diverse sezioni o pagine. Oppure possiamo attirare l’attenzione dell’utente suggerendo in che modo eseguire una certa azione e dando immediato feedback in risposta a ciascun passaggio completato.

Nel realizzare delle animazioni e delle transizioni bisogna comunque prestare molta attenzione a non peggiorare le prestazioni dell’applicazione.

È inoltre opportuno non aggiugere transizioni o animazioni superflue che finiscono per introdurre difficoltà o impedimenti alla naturale interazione e navigazione da parte dell’utente.

Differenza fra transizioni e animazioni

Prima di iniziare è bene soffermarci un attimo per spiegare quali sono le differenze sostanziali fra transizioni ed animazioni. Diciamo fin da subito che questa lezione sarà in gran parte incentrata sulle transizioni, ma abbiamo deciso di dedicare una breve sezione per illustrare come utilizzare delle animazioni CSS in Vue.js.

Con le transizioni effettuiamo il passaggio fra due soli stati in modo fluido interpolando i valori delle proprietà dello stile che caratterizzano uno o più elementi.

Le animazioni si differenziano dalle transizioni perché solitamente prevedono il passaggio fra un numero maggiore di stati. In molti casi le animazioni sono più complesse delle transizioni. Spesso è necessario implementare delle animazioni direttamente in Javascript per sopperire alle mancanze degli strumenti offerti dal CSS. Mentre le transizioni si occupano di completare il passaggio diretto fra uno stato iniziale ed uno finale, le animaizoni possono avere invece una serie di stati intermedi.

Nelle situazioni più semplici, le animazioni hanno la possibilità di comportarsi come delle transizioni definendo solo due stati fra cui interpolare dei valori, al contrario le transizioni non possono avere degli stati intermedi come avviene per le animazioni.

Transizioni in Vue.js

Abbiamo visto che le transizioni servono per passare in modo graduale da uno stato iniziale ad uno finale. Consideriamo per esempio il caso di un elemento con opacità iniziale pari a 0. Invece di impostare direttamente il valore di opacità a 1 e fare apparire l’elemento all’improvviso sullo schermo, possiamo invece incrementare il valore dell’opacità un po’ alla volta in un certo arco di tempo ed assicurare una transizione meno brusca fra i due stati.

Sulla base dello stesso principio possiamo spostare un elemento da un punto iniziale ad uno finale senza farlo ‘sparire’ e ‘riapparire’ magicamente nella posizione finale. In questo modo il movimento sarà più fluido e naturale.

Per definire correttamente il processo di transizione da uno stato all’altro, dovremo applicare delle classi CSS al momento opportuno.

In nostro aiuto, Vue.js fornisce il componente transition per implementare delle transizioni di ingresso e di uscita di un elemento dal DOM come avviene quando utilizziamo le direttive v-if e v-show. Il componente transition si occuperà di aggiungere correttamente le classi CSS necessarie durante il processo di transizione.

Andremo quindi ad inserire gli elementi su cui vogliamo applicare la transizione fra i tag di apertura e di chiusura di <transition></transition> il quale consente di configurare la transizione attraverso una serie di attributi.

Supponiamo allora di avere un elemento che inizialmente non è presente nel DOM e che vogliamo far apparire gradualmente sullo schermo.

Possiamo pensare di implementare una transizione in cui l’elemento passa da uno stato iniziale con opacità 0 ad uno stato finale con opacità 1. Tale passaggio avviene durante un certo intervallo di transizione per il quale dobbiamo stabilire la durata e il tipo di andamento della transizione nel corso del tempo (easing).

tipo di transizione in ingresso

Tramite <transition> Vue.js applica correttamente delle classi CSS in corrispondenza di ciascuno stato e durante l’intero corso della transizione. I nomi di tali classi sono preceduti dal prefisso ‘v-‘ e sono:

  • v-enter per lo stato iniziale. La classe viene rimossa non appena l’elemento viene inserito.
  • v-enter-to per lo stato finale. Viene aggiunta dopo che l’elemento è stato inserito e viene rimossa al termine della transizione.
  • v-enter-active viene applicata durante l’intera fase di transizione ed è la classe in cui solitamente specifichiamo i parametri di configurazione della transizione (proprietà, durata, easing).
classi applicate durante la transizione in ingresso

Per eseguire poi il passaggio inverso durante la rimozione di un elemento dal DOM, possiamo definire una transizione per diminuire l’opacità di un elemento dal valore 1 fino a 0.

tipo di transizione in uscita

Anche in questo caso <transition> provvede ad applicare opportunamente delle classi CSS nelle varie fasi della transizione. Nello specifico verranno applicate le seguenti classi.

  • v-leave per lo stato iniziale in cui l’elemento è ancora visibile sullo schermo. La classe viene aggiunta non appena viene avviata la transizione e rimossa nel frame successivo.
  • v-leave-to per lo stato finale. Viene aggiunta nel momento in cui viene rimossa v-leave e viene rimossa non appena la transizione termina.
  • v-leave-active viene applicata durante l’intera fase di transizione. Anche in questo caso sfrutteremo questa classe per configurare la transizione (proprietà, durata, easing).
classi applicate durante la transizione in ingresso

In totale abbiamo 6 classi che vengono automaticamente applicate da Vue per implementare le transizioni di ingresso e uscita di un elemento. A noi toccherà invece il compito di definire le caratteristiche della transizione e stabilire a quali proprietà deve essere applicata.

Vediamo allora un esempio pratico per chiarire meglio quanto abbiamo illustrato finora. Partiamo creando un semplice componente che fa uso solo della direttiva v-if per aggiungere o rimuovere un paragrafo dal DOM. Cliccando su un apposito pulsante viene invocato il metodo hideOrShow() che inverte il valore di una proprietà booleana isVisible. Questa viene poi assegnata alla direttiva v-if applicata sul paragrafo. Iniziamo ad implementare il componente senza far uso di alcuna transizione, vedremo poi in un secondo momento come aggiungere una transizione attraverso <transition>.

(Se vuoi, puoi provare a realizzare l’esempio secondo le specifiche indicate sopra e continuare questa lezione in un secondo momento per confrontare la tua soluzione con quella da noi proposta.)

All’interno di un nuovo file MyComponent.vue, inseriamo il seguente frammento di codice.

<template>
  <div>
    <button @click="hideOrShow">{{ buttonLabel }}</button>
    <p v-if="isVisible">
      Nulla vitae elit libero, a pharetra augue.
    </p>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        isVisible: true
      }
    },
    computed: {
      buttonLabel() {
        return this.isVisible ? 'Hide' : 'Show';
      }
    },
    methods: {
      hideOrShow() {
        this.isVisible = !this.isVisible;
      }
    }
  }
</script>

Se proviamo a visualizzare il componente appena creato nel browser attraverso la funzione di Instant Prototyping, vediamo che il paragrafo appare o scompare dalla pagina all’improvviso.

Possiamo invece applicare una semplice transizione e fare apparire progressivamente il paragrafo aumentando la sua opacità da 0 a 1. Viceversa, riporteremo quest’ultima al valore 0 facendo dissolvere il paragrafo durante un breve arco di tempo.

Per ottenere il risultato descritto, inseriamo <transition> all’interno del template di MyComponent ed aggiungiamo una sezione per le regole CSS in cui andiamo a definire il comportamento della transizione attraverso le 6 classi (v-enter, v-enter-to…) viste in precedenza.

Volendo essere più precisi, possiamo sfruttare l’attributo name di <transition> per personalizzare il nome delle classi della transizione. Così facendo, il prefisso v- viene sostituito dal nome da noi indicato. Nel nostro caso scegliamo il nome ‘fade’ ed abbiamo quindi accesso alle classi fade-enter, fade-enter-to, fade-enter-active per la transizione d’ingresso e alle classi fade-leave, fade-leave-to, fade-leave-active per la transizione d’uscita.

Modifichiamo allora il template del nostro componente come mostrato sotto.

<template>
  <div>
    <button @click="hideOrShow">{{ buttonLabel }}</button>
    <!-- Usiamo il wrapper <transition></transition> -->
    <!-- intorno all'elemento su cui applichiamo -->
    <!-- la transizione -->
    <transition name="fade">
      <p v-if="isVisible">
        Nulla vitae elit libero, a pharetra augue.
      </p>
    </transition>
  </div>
</template>

Sempre all’interno dello stesso file, andiamo poi ad aggiungere le diverse regole per le 6 classi necessarie alla transizione.

<style scoped>
/* A breve vedremo come ottimizzare */
/* il seguente frammento di codice */
.fade-enter {
  opacity: 0;
}

.fade-enter-to {
  opacity: 1;
}

.fade-enter-active {
  transition: opacity 500ms ease-out;
}

.fade-leave {
  opacity: 1;
}

.fade-leave-to {
  opacity: 0;
}

.fade-leave-active {
  transition: opacity 500ms ease-out;
}
</style>

Quando il paragrafo viene inserito nel DOM avrà un’opacità iniziale pari a 0. Tale valore viene lentamente incrementato fino ad 1 durante un intervallo di 500ms. Il passaggio inverso avviene invece durante la fase di rimozione del paragrafo dal DOM.

Vediamo allora il risultato ottenuto all’interno del browser.

https://vimeo.com/462644161

Rivedendo il codice scritto in precedenza per le regole CSS, ci accorgiamo che è possibile riscrivere le regole delle diverse classi raggruppando quelle che hanno lo stesso blocco di dichiarazioni. In questo modo semplifichiamo la sezione <style> del componente ed evitiamo inutili ripetizioni.

<style scoped>
.fade-enter, .fade-leave-to {
  opacity: 0;
}

.fade-enter-to, .fade-leave {
  opacity: 1;
}

.fade-enter-active, .fade-leave-active {
  transition: opacity 500ms ease-out;
}
</style>

Esaminando le regole CSS appena viste, notiamo però che per le classi .fade-enter-to (stato finale della transizione in ingresso) e .fade-leave (stato iniziale della transizione in uscita) usiamo la dichiarazione opacity: 1; che è in realtà il valore predefinito di qualsiasi elemento del DOM. Per questa ragione possiamo rimuovere l’intera regola perché è superflua. Alla fine nel file MyComponent.vue avremo quindi il seguente frammento di codice.

// file: MyComponent.vue
<template>
  <div>
    <button @click="hideOrShow">{{ buttonLabel }}</button>
    <!-- Usiamo il wrapper <transition></transition> -->
    <!-- intorno all'elemento su cui applichiamo -->
    <!-- la transizione -->
    <transition
      name="fade"
    >
      <p v-if="isVisible">
        Nulla vitae elit libero, a pharetra augue.
      </p>
    </transition>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        isVisible: true
      }
    },
    computed: {
      buttonLabel() {
        return this.isVisible ? 'Hide' : 'Show';
      }
    },
    methods: {
      hideOrShow() {
        this.isVisible = !this.isVisible;
      }
    }
  }
</script>

<style scoped>
.fade-enter, .fade-leave-to {
  opacity: 0;
}

.fade-enter-active, .fade-leave-active {
  transition: opacity 500ms ease-out;
}
</style>

Classi personalizzate per le transizioni

Per integrarsi meglio con librerie di terze parti, Vue offre anche la possibilità di sovrascrivere i nomi di ognuna delle classi che vengono applicate durante il processo di transizione. Per usare delle classi personalizzate, basta applicare uno o più attributi al componente <transition> fra quelli elencati sotto.

  • enter-class
  • enter-active-class
  • enter-to-class
  • leave-class
  • leave-active-class
  • leave-to-class

Vediamo allora un esempio simile a quello creato in precedenza. In questo caso useremo però la libreria Animate.css che presenta una ricca e curata raccolta di effetti.

All’interno di una nuova cartella, lanciamo il comando npm init -y per creare un file package.json con le impostazioni predefinite. Installiamo poi la libreria Animate.css col comando npm install animate.css. Fatto ciò creiamo un nuovo file TestComponent.vue in cui inseriamo il seguente template.

<template>
  <div>
    <button @click="hideOrShow">{{ buttonLabel }}</button>
    <!-- Usiamo il wrapper <transition></transition> -->
    <!-- intorno all'elemento su cui applichiamo -->
    <!-- la transizione -->
    <transition
      name="custom-classes"
      enter-active-class="animate__animated animate__bounceInUp"
      leave-active-class="animate__animated animate__hinge"
    >
      <p v-if="isVisible">
        Nulla vitae elit libero, a pharetra augue.
      </p>
    </transition>
  </div>
</template>

Notiamo che sul componente transition abbiamo applicato due attributi enter-active-class e leave-active-class a cui abbiamo assegnato come valore il nome di due classi. La documentazione di Animate.css ricorda di usare sempre la classe animate__animated insieme ad una delle classi che attivano l’opportuna animazione. Per come è stato implementato, il paragrafo entrerà dal basso verso l’alto rimbalzando e verrà rimosso oscillando intorno all’origine che è posizionata sul lato sinistro del paragrafo stesso.

Prima di poterle usare, dobbiamo ovviamente ricordarci di importare le regole della libreria fra i tag <style> del componente.

<style scoped>
  @import './node_modules/animate.css/animate.min.css';
</style>

Il codice dell’intero componente è quindi il seguente.

<template>
  <div>
    <button @click="hideOrShow">{{ buttonLabel }}</button>
    <!-- Usiamo il wrapper <transition></transition> -->
    <!-- intorno all'elemento su cui applichiamo -->
    <!-- la transizione -->
    <transition
      name="custom-classes"
      enter-active-class="animate__animated animate__bounceInUp"
      leave-active-class="animate__animated animate__hinge"
    >
      <p v-if="isVisible">
        Nulla vitae elit libero, a pharetra augue.
      </p>
    </transition>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        isVisible: true
      }
    },
    computed: {
      buttonLabel() {
        return this.isVisible ? 'Hide' : 'Show';
      }
    },
    methods: {
      hideOrShow() {
        this.isVisible = !this.isVisible;
      }
    }
  }
</script>

<style scoped>
  @import './node_modules/animate.css/animate.min.css';
</style>

Possiamo visualizzare il nostro componente all’interno del browser usando sempre la funzione di Instant Prototyping (vue serve TestComponent.vue ).

https://vimeo.com/462644069

Animazioni CSS

Per creare ed applicare delle animazioni CSS usiamo una tecnica simile a quella già vista per le transizioni. L’unica differenza da ricordare è che in questo caso la classe v-enter viene rimossa quando si verifica l’evento animationend al termine dell’animazione.

Riprendiamo allora il solito esempio e vediamo come creare una semplice animazione in CSS utilizzando @keyframes.

Il primo passo da eseguire consiste nell’assegnare un nome personalizzato attraverso l’attributo name del componente <transition>. Nel nostro caso usiamo il nome bounce-and-fade.

<template>
  <div>
    <button @click="hideOrShow">{{ buttonLabel }}</button>
    <!-- Usiamo il wrapper <transition></transition> -->
    <!-- intorno all'elemento su cui applichiamo -->
    <!-- la transizione -->
    <transition
      name="bounce-and-fade"
    >
      <p v-if="isVisible">
        Nulla vitae elit libero, a pharetra augue.
      </p>
    </transition>
  </div>
</template>

Dobbiamo poi selezionare l’animazione da eseguire durante le due fasi di ingresso e di uscita. Per questo motivo ci serviamo delle due classi .bounce-and-fade-enter-active e .bounce-and-fade-leave-active e definiremo le animazioni attraverso @keyframes.

<style scoped>
.bounce-and-fade-enter-active {
  animation: bounce-and-fade 500ms ease-out;
}

.bounce-and-fade-leave-active {
   animation: slide-and-fade 500ms ease;
}

@keyframes bounce-and-fade {
  0% {
    transform: translateY(200px);
    opacity: 0;
  }
  95% {
    transform: translateY(-10px);
    opacity: .95;
  }
  100% {
    transform: translateY(0px);
    opacity: 1;
  }
}

@keyframes slide-and-fade {
  0% {
    transform: translateX(0px);
    opacity: 1;
  }
  10% {
    transform: translateX(-20px);
    opacity: .95;
  }
  100% {
    transform: translateX(100vw);
    opacity: 0;
  }
}
</style>

Per completezza riportiamo l’intero codice del componente.

<template>
  <div>
    <button @click="hideOrShow">{{ buttonLabel }}</button>
    <!-- Usiamo il wrapper <transition></transition> -->
    <!-- intorno all'elemento su cui applichiamo -->
    <!-- la transizione -->
    <transition
      name="bounce-and-fade"
    >
      <p v-if="isVisible">
        Nulla vitae elit libero, a pharetra augue.
      </p>
    </transition>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        isVisible: true
      }
    },
    computed: {
      buttonLabel() {
        return this.isVisible ? 'Hide' : 'Show';
      }
    },
    methods: {
      hideOrShow() {
        this.isVisible = !this.isVisible;
      }
    }
  }
</script>

<style scoped>
.bounce-and-fade-enter-active {
  animation: bounce-and-fade 500ms ease-out;
}

.bounce-and-fade-leave-active {
   animation: slide-and-fade 500ms ease;
}

@keyframes bounce-and-fade {
  0% {
    transform: translateY(200px);
    opacity: 0;
  }
  95% {
    transform: translateY(-10px);
    opacity: .95;
  }
  100% {
    transform: translateY(0px);
    opacity: 1;
  }
}

@keyframes slide-and-fade {
  0% {
    transform: translateX(0px);
    opacity: 1;
  }
  10% {
    transform: translateX(-20px);
    opacity: .95;
  }
  100% {
    transform: translateX(100vw);
    opacity: 0;
  }
}
</style>

E ancora una volta possiamo visualizzare il risultato ottenuto nel browser.

https://vimeo.com/462644162

Transizioni fra elementi e componenti diversi

Nelle precedenti lezioni abbiamo visto come servisi della direttiva v-if in combinazione con v-else per alternare uno di due elementi (o gruppi di elementi) al verificarsi di una certa condizione.

Vediamo ora in che modo implementare una transizione fra due elementi che utilizzano v-if e v-else. Nel caso di elementi dello stesso tipo è importante applicare un attributo univoco key altrimenti Vue provvederà a sostituire solo il contenuto dell’elemento e non verrà quindi applicata la transizione. Il team di Vue consiglia di utilizzare comunque l’attributo key nel caso di elementi multipli presenti fra i tag <transition>.

Creiamo allora un semplice esempio in cui visualizziamo solo uno di due paragrafi in base al valore di una proprietà current che viene aggiornata ogni volta che si preme un pulsante.

<template>
  <div>
    <button @click="change">Cambia paragrafo</button>
    <transition name="fade">
      <p key="1" v-if="current === 0">
        Paragrafo 1
      </p>
      <p  key="2" v-else>paragrafo 2</p>
    </transition>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        current: 0
      }
    },
    methods: {
      change() {
        this.current = this.current === 0 ? 1 : 0;
      }
    }
  }
</script>

<style scoped>
.fade-enter-active, .fade-leave-active {
  transition: opacity 1000ms ease-out;
}

.fade-enter, .fade-leave-to {
  opacity: 0;
}
</style>

Se però visualizzimo il risultato dell’esempio appena creato nel browser, ci accorgiamo che la sostituzione degli elementi provoca un effetto che probabilmente non è quello sperato. Infatti il paragrafo non visibile viene aggiunto al DOM mentre è ancora presente l’altro.

https://vimeo.com/462644223

Per risolvere questo tipo di comportamento indesiderato, possiamo applicare un altro attributo su <transition>, ovvero mode che può avere due diversi valori e permette di indicare in che modo deve avvenire la transizione fra componenti.

  • se mode è pari a in-out, viene completata la transizione in ingresso del nuovo elemento e, una volta terminata, si procede alla transizione in uscita del secondo elemento.
  • in modalità out-in si procede invece alla transizione in uscita dell’elemento visibile per poi eseguire la transizione in ingresso del nuovo elemento.

La prima modalità è usata raramente, al contrario è molto più frequente la modalità out-in.

Correggiamo allora il nostro esempio per ottenere il comportamento desiderato.

<transition name="fade" mode="out-in">
  <p key="1" v-if="current === 0">
    Paragrafo 1
  </p>
  <p  key="2" v-else>Paragrafo 2</p>
</transition>
https://vimeo.com/462644320

È possibile semplificare ulteriormente l’esempio appena mostrato.

Infatti, per le transizioni fra elmenti simili che si differenziano per il solo valore dell’attributo key, possiamo sfruttare quest’ultimo insieme alla direttiva v-bind e rimuovere la coppia v-if/v-else come mostrato nell’esempio sottostante.

<template>
  <div>
    <button @click="change">Cambia paragrafo</button>
    <transition name="fade" mode="out-in">
      <p :key="current">
        Paragrafo {{ current + 1}}
      </p>
    </transition>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        current: 0
      }
    },
    methods: {
      change() {
        this.current = this.current === 0 ? 1 : 0;
      }
    }
  }
</script>

<style scoped>
.fade-enter-active, .fade-leave-active {
  transition: opacity 500ms ease-out;
}

.fade-enter, .fade-leave-to {
  opacity: 0;
}
</style>

Per quanto riguarda i componenti, grazie a <component> possiamo implementare una transizione in pochi passaggi senza doverci neanche preoccupare di settare un attributo key univoco.

Vediamo allora un altro esempio ed avendo già incontrato <component> nelle lezioni precedenti parlando dei componenti dinamici, è facile ed immediato comprendere il frammento di codice riportato sotto.

// file: ComponentA.vue
<template>
  <div>
    Component A
  </div>
</template>
// file: ComponentB.vue
<template>
  <div>
    Component B
  </div>
</template>
// file: MyComponent.vue
<template>
 <div>
   <button @click="changeComponent">Cambia componente</button>
   <transition name="fade" mode="out-in">
     <component :is="currentComponent"></component>
   </transition>
 </div>
</template>

<script>
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';

export default {
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  },
  data() {
    return {
      currentComponent: 'component-a'
    }
  },
  methods: {
    changeComponent() {
      this.currentComponent = this.currentComponent === 'component-a' ?
        'component-b' :
        'component-a';
    }
  }
}
</script>

<style scoped>
.fade-enter-active, .fade-leave-active {
  transition: opacity 500ms ease-out;
}
.fade-enter, .fade-leave-to {
  opacity: 0;
}
</style>

Transizioni per liste di elementi

Grazie al componente <transition> possiamo implementare delle transizioni per un singolo nodo del DOM. Anche nei casi in cui effettuiamo delle transizioni fra 2 elementi, viene effettuato il rendering di un solo nodo alla volta.

Per liste di elementi inseriti contemporaneamente nel DOM useremo invece <transition-group> che presenta alcune differenze rispetto a <transition>, ovvero:

  • Aggiunge nel DOM un elemento contenitore che possiamo personalizzare attraverso l’attributo tag. I nodi del DOM inseriti fra i tag di apertura e chiusura <transition-group></transition-group> verranno quindi racchiusi all’interno dell’elemento specificato dall’attributo tag. Se non indicato in maniera esplicità, verrà usato l’elemento predefinito <span>.
  • Non è possibile utilizzare l’attributo mode per specificare la modalità di transizione visto che non si sostituiscono in questo caso due singoli elementi.
  • I nodi inseriti fra i tag <transition-group></transition-group> devono sempre avere un attributo key univoco.
  • Le classi CSS delle transizioni vengono applicate a ciascuno degli elementi presenti fra i tag <transition-group></transition-group> e non all’elemento che li contiene.

Vediamo allora come realizzare un esempio illustrativo in cui aggiungiamo degli elementi ad una lista attraverso un campo di tipo <input type="text">. Implementiamo poi delle semplici transizioni di ingresso e uscita per gli elementi della lista.

https://vimeo.com/462644220

Dal video riportato sopra, notiamo un’altra caratteristica del componente <transition-group> che consente di applicare delle transizioni anche per gli elementi che si spostano dalla loro posizione originale. A questi ultimi viene infatti aggiunta la classe v-move. Se specificato, il valore dell’attributo name sostituisce anche in questo caso il prefisso ‘v-‘.

Riportiamo allora di seguito il codice del prossimo esempio.

<template>
  <div>
    <input 
      type="text" 
      v-model="currentValue">
    <button @click="add">
      Aggiungi alla lista
    </button>
    <transition-group 
      name="list-item" 
      tag="ul"
    >
      <li
        @click="remove(item.id)"
        v-for="item in list" 
        :key="item.id">
        {{ item.value }}
      </li>
    </transition-group>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        currentValue: '',
        list: []
      }
    },
    methods: {
      add() {
        if (this.currentValue) {
          this.list.push(this.createNewListItem(this.currentValue));
          this.currentValue = '';
        }
      },
      remove(id) {
        this.list = this.list.filter(element => element.id !== id);
      },
      createNewListItem(value) {
        return {
          id: Date.now(),
          value
        }
      }
    }
  }
</script>

<style scoped>
  ul {
    list-style: none;
    padding: 0;
    font-family: 'Roboto', Arial, sans-serif;
  }

  li {
    display: block;
    width: 80%;
    margin-top: 1.5rem;
    color: hsl(211, 55%, 10%);
    border: 1px solid hsl(211, 51%, 18%);
    border-radius: 8px;
    padding: 1rem;
    cursor: pointer;
    
  }

  li:hover {
    border: 1px solid hsl(357, 63%, 37%);
    color: hsl(357, 63%, 37%);
    background: hsl(356, 61%, 90%);
  }
  
  li:hover::after {
    content: '1F5D1';
    width: 32px;
    height: 32px;
    float: right;
  }

  /* 
  *   classi per le transizioni 
  */
  .list-item-enter-active,
  .list-item-leave-active,
  .list-item-move {
    transition: 500ms ease-out;
    transition-property: opacity, transform;
  }

  .list-item-leave-active {
    position: absolute;
  }

  .list-item-enter, .list-item-leave-to {
    opacity: 0;
    transform: translateX(40px) scale(.8);
  }

  .list-item-enter-to {
    opacity: 1;
    transform: translateX(0);
  }
</style>

All’interno del template abbiamo usato <transition-group> con due attributi: name per indicare quale deve essere il prefisso delle classi applicate durante le transizioni e tag per selezionare quale nodo contenitore deve racchiudere i vari elementi <li>. Questi ultimi hanno un attributo univoco key.

Per quanto riguarda invece le classi CSS, l’unica differenza rispetto agli esempi precedenti deriva dall’introduzione della classe .list-item-move in cui definiamo le caratteristiche della transizione da applicare agli elementi in movimento.

È importante notare che durante la fase di rimozione (.list-item-leave-active ) dal DOM la proprietà CSS position degli elementi <li> assume valore pari a absolute. In questo modo l’elemento che sta per essere eliminato, viene rimosso dal normale flusso del DOM permettendo agli altri elementi sottostanti di spostarsi verso l’alto per occupare la nuova posizione.

Transizioni tramite Javascript

Dopo aver visto come implementare delle transizioni ed animazioni in puro CSS usufruendo delle classi che vengono automaticamente aggiunte e rimosse da Vue, vediamo brevemente quali strumenti vengono messi a nostra disposizione per implementare delle transizioni e animazioni più o meno complesse tramite javascript.

Per definire delle transizioni in javascript per gli elementi che vengono inseriti o rimossi dal DOM useremo una serie di funzioni hooks speciali. Per specificare quali metodi devono essere invocati durante la transizione al verificarsi di un preciso evento, ci serviamo della direttiva v-on applicata sul componente <transition>. A tale direttiva passiamo come argomento il nome di uno degli eventi che vengono emessi da Vue durante la fase di ingresso o di uscita di un elemento racchiuso fra i tag <transition></transition>

<transition
  v-on:before-enter="beforeEnter"
  v-on:enter="enter"
  v-on:after-enter="afterEnter"
  v-on:enter-cancelled="enterCancelled"

  v-on:before-leave="beforeLeave"
  v-on:leave="leave"
  v-on:after-leave="afterLeave"
  v-on:leave-cancelled="leaveCancelled"
  
  :css="false"
>
  <!-- ... -->
</transition>

Ciascun metodo riceve come primo argomento un riferimento all’elemento che viene aggiunto o rimosso dal DOM. Per gli eventi enter e leave viene passato anche un secondo argomento done che è una funzione da invocare per segnalare la fine della transizione. Tale funzione è opzionale solo nei casi in cui le transizioni javascript vengono usate in combinazione con delle regole CSS per le classi che vengono applicate in fase di transizione.

Nell’esempio riportato soprta aggiungiamo invece v-bind:css="false" al componente <transition> per segnalare che useremo soltanto Javascript per la transizione. In questo modo ci assicuriamo che eventuali regole CSS non interferiscano accidentalmente con la transizione da noi definita.

// ...
methods: {
  /*
  * Transizioni in ingresso
  */

  beforeEnter: function (el) {},
  enter: function (el, done) {
    done()
  },
  afterEnter: function (el) {},
  enterCancelled: function (el) {},

  /*
  * Transizioni in uscita
  */

  beforeLeave: function (el) {},
  leave: function (el, done) {
    done()
  },
  afterLeave: function (el) {},
  leaveCancelled: function (el) {}
}

Per concludere questa lezione vediamo allora un breve esempio in cui implementiamo una transizione in ingresso ed uscita per un paragrafo che viene inserito o rimosso dal DOM in base al valore di una proprietà booleana.

Per prima cosa creiamo una nuova cartella all’interno della quale lanciamo il comando npm init -y per inizializzare il file package.json con le impostazioni predefinite.

Installiamo poi la libreria Velocity con il comando npm i velocity-animate. (In alternativa avremmo potuto usare GSAP che fornisce un’API intuitiva e ben documentata.)

Creiamo poi un nuovo file MyComponent.vue in cui inseriamo il seguente frammento di codice.

<template>
  <div>
    <button @click="toggle">{{ buttonLabel }}</button>
    <transition
      @before-enter="beforeEnter"
      @enter="enter"
      @leave="leave"

      :css="false"
    >
      <p v-if="visible">
        Donec ullamcorper nulla non metus auctor fringilla.
      </p>
    </transition>
  </div>
</template>

<script>
  import Velocity from 'velocity-animate';

  export default {
    data() {
      return {
        visible: true
      }
    },
    computed: {
      buttonLabel() {
        return this.visible === true ? 'Nascondi' : 'Mostra';
      }
    },
    methods: {
      toggle() {
        this.visible = !this.visible; 
      },
      beforeEnter(el) {
        Velocity(el, 
          {
            opacity: 0, 
            scale: .5, 
            translateY: '50px'
          }, 
          { duration: 0}
        );
      },
      enter(el, done) {
        Velocity(el, 
          {
            opacity: 1,
            scale: 1,
            translateY: '0px'
          }, 
          { 
            duration: 500, 
            easing: [0.68,-0.71,.09,1.94], 
            complete: done
          });
      },
      leave(el, done) {
        Velocity(el, 
          { 
            rotateZ: '720deg', 
            scale: .2, 
            opacity: 0 
          }, 
          { 
            duration: 800, 
            complete: done 
          });
      }
    }
  }
</script>

<style scoped>
  div {
    text-align: center;
  }
</style>

Tramite Velocity settiamo le proprietà del paragrafo prima che venga inserito nel DOM, allo stesso modo indichiamo all’interno del metodo enter() il tipo di transizione da eseguire in ingresso. In questo caso abbiamo usato una particolare curva di Bézier per personalizzare l’andamento della transizione. Così facendo il paragrafo tenderà a ‘rimbalzare’ leggermente prima di assestarsi nella posizione finale. Viceversa, quando viene rimosso dal DOM, il paragrafo verrà ridimensionato e nello stesso tempo ruoterà su sé stesso fino a dissolversi.

https://vimeo.com/462644198

Riepilogo

In questa lezione abbiamo illustrato superficialmente in che modo realizzare delle semplici transizioni ed animazioni in Vue.js. Abbiamo realizzato dei semplici esempi utilizzando <transition> e <transition-group>. Abbiamo infine presentato alcune funzioni speciali che possiamo sfruttare per realizzare delle transizioni più complesse direttamente in Javascript. Nella prossima lezione parleremo di Vuex che consente di gestire lo stato di applicazioni anche complesse in modo facile ed intuitivo.

Pubblicitร