Animare contenuti dinamici usando css e javascript

Basta problemi con le animazioni web.

Grafica di un animazione

Stai avendo problemi con le animazioni web? Hai provato usando delle css transition, poi hai provato usando qualche keyframe, magari hai usato le nuove web animations api. Ma è tutto inutile, i tuoi elementi non si animano.

Cerchiamo di capire il problema.

<div class="panel">
<button class="btn-open">Animate</button>
</div>
Code language: HTML, XML (xml)
.panel {
padding: 1rem;
background-color: #a5d6a7;
box-shadow: 5px 0px 5px rgba(0, 0, 0, 0.32);
height: auto;
transition: height 1s ease-out;
}

.panel.open {
height: 500px;
}
Code language: CSS (css)
document.querySelector('.btn-open').addEventListener('click', () => {
document.querySelector('.panel').classList.toggle('open');
});
Code language: JavaScript (javascript)

Questo pezzo di codice, mostrerà il sequente contenuto.

Page rendered by the above code.

Ma se provi a fare click sul pulsante ‘Animate’, il div si espanderà in un solo frame, niente animazioni, nonostante la transition. Questo perchè il CSS non permette animazioni che coinvolgono dimensioni dinamiche, tipo appunto height: auto.

Quello che potresti fare è definire un’altezza precisa, ma…non sempre è possibile, visto che il contenuto dinamico (ad esempio del testo) potrebbe andare in overflow.

Usa il transform

Proviamo la prima soluzione

.panel {
transform-origin: top left;
transform: scale(1, 1);
transition: transform 1s ease-out;
}

.panel.open {
transform: scale(1, 2);
}
Code language: CSS (css)

Quello che sta succedendo qua, è che invece di definire un altezza sto usando la proprietà transform. Questa ti permette di “allungare” ed allargare il div, usando il valore scale(x, y).

Abbiamo 2 problemi però.

  • Non puoi definire una scala usando i px o qualsiasi altra unità css
  • Letteralmente, deformi qualsiasi cosa si trovi all’interno del div
Expanded panel but with stretched content also

La tecnica FLIP

Proviamo qualcosa di diverso, useremo la tecnica FLIP (First, Last, Invert, Play). Questa ci permetterà di animare qualsiasi contenuto dinamico, al costo di usare un po’ di codice js.

Allora, andremo ad usare la proprietà height per definire lo stato “open”, e come potrai vedere non ho definito nessuna altezza nello stato “normale” (poiché il default è auto). Ho anche separato la proprietà transition in un’altra classe, applicandola sul transform.

.panel {
padding: 1rem;
background-color: #a5d6a7;
box-shadow: 5px 0px 5px rgba(0, 0, 0, 0.32);
}

.panel.animating {
transition: transform 1s ease-out;
}

.panel.open {
height: 100px;
}
Code language: CSS (css)

Ecco la parte interessante.

// Get the first dimensions
const first = panel.getBoundingClientRect();

// Apply the layout change
panel.classList.add('open');

// Get the last dimensions
const last = panel.getBoundingClientRect();

// Invert
const invertedHeight = first.height / last.height;
panel.style.transformOrigin = 'top left';
panel.style.transform = `scale(1, ${invertedHeight})`;

// Play
requestAnimationFrame(() => {
panel.classList.add('animating');
panel.style.transform = 'none';

panel.addEventListener('transitionend', () => {
panel.classList.remove('animating');
});
});
Code language: JavaScript (javascript)

OK, lo so… sembra molto codice per una piccola animazione, ma questo pezzo di codice ti permetterà di animare contenuti in modo veloce e dinamico. (NOTA: usare transition sul transform è sempre preferibile per le performance)

So anche che potrebbe essere complicato capire cosa sta succedendo, riassumendo la logica è questa:

  • Prendere l’altezza del div nello stato normale (First)
  • Applicare il cambio di layout (aggiungi una classe, un elemento, etc..)
  • Prendere l’altezza dopo questo cambio (Last)
  • Applicare un transform per invertire il cambio (Invert)

Quindi, a questo punto siamo di nuovo all’inizio, il div dovrebbe avere le stesse dimensioni che avevamo prima del cambiamento, ma stavolta applicate con un transform, e il transform è una proprietà facilmente animabile (non come l’altezza).

Al frame successivo (requestAnimationFrame) aggiungiamo la classe animating e impostiamo il transform a none (o scale(1, 1)). Questo farà partire l’animazione, che si fermerà quando il div avrà le dimensioni finali (height: 100px). A questo punto, giusto per pulire, all’evento transitionend, rimuoviamo la classe animating. (Nota: il codice sopra non è ottimizzato, l’evento transitionend dovrebbe essere rimosso, usando o l’opzione once o il metodo removeEventListener).

La tecnica FLIP, può essere usata per animare anche la larghezza e la posizione di un elemento. Potrebbe essere usata per qualsiasi cambiamento che modifica le dimensioni dell’elemento.

Un’altra cosa interessante è che l’elemento finale (Last) non deve per forza essere quello iniziale.

Svantaggi di FLIP

Potresti aver notato che con il codice sopra, durante (e solo durante) l’animazione il contenuto si deforma, questo perché usiamo il transform. L’effetto si può nascondere usando un’animazione veloce, o altre animazioni tipo sull’opacità del contenuto.

Questo unico contro può essere un limite, per questo scriverò in un articolo futuro di un’altra tecnica che ci permetterà di superarlo.

Conclusioni

Per riassumere, abbiamo imparato ad usare la tecnica FLIP, che è molto potente, ma con qualche svantaggio. Spero che ti sei interessato e che approfondirai questa tecnica.

Non ho i commenti su questo blog (ci sto lavorando) ma ti incoraggio a scrivermi un email , o contattarmi su qualsiasi social così che tu possa darmi un feedback.

Alla prossima!