Admin/Dev

28
Déc.
2017

Astuce CSS : Animer ses éléments HTML avec des transitions - Partie 3

Publié par sky

Maintenant que nous maitrisons à la perfection la création de transition en CSS, nous allons voir comment aller encore plus loin et les mélanger avec des scripts javascript, comme cela peut être nécessaire dans une application web par exemple.

Que ce soit en changeant de style ou carrément de classe de style, il est possible d'activer une transition avec un script Javascript.

Pour commencer, créons un fichier HTML basique, comme dans les deux premières parties, avec dedans un div qui sera notre carré, et pour pourvoir l'appeler depuis notre script, nous allons lui donner un identifiant, ici "myObject".

Appliquons lui les styles nécessaires, y compris notre transition.

#myObject {position:absolute; left:100px; top:100px; width:100px; height:100px; background:#CC3333; transition:all 1s linear;}

Maintenant ajoutons une classe de style sans lui appliquer

#myObject.toGreen {background:#33CC33;}

Il nous suffit maintenant d'écrire le script qui changera la classe dynamiquement

document.getElementById('myObject').className = 'toGreen';

Dès détection du changement d'état, le navigateur appliquera la transition.
Il est possible d'ajouter ce script sur n'importe quel événement pour le rendre plus intéressant.

A noter que comme indiqué précédement il est possible de modifier le style directement sans forcément appliquer une classe, produira le même effet

document.getElementById('myObject').style.background = '#33CC33';

Cependant dès que vous souhaitez modifier plus d'une propriété à la fois, il est préférable d'utiliser une classe.

Mais pour passer d'un état à un autre, tel que nous venons de le faire, il y a souvent une solution en pure CSS, notamment, avec les pseudos classes. Là ou le script devient intéressant, c'est lorsque nous avons des animations complexes, avec plusieurs états à gérer et des événements non-initiés par l'utilisateur.

Approfondissont donc avec un cas pratique, toujours plus parlant que la seule théorie.

Nous allons demander à notre carré de faire un petit parcours. Nous allons lui faire faire un trajet carré, il s'agit d'une forme simple, qu'il est aisé de décomposer et donc d'animer.

Notre objet contient déjà les styles fixes, qui ne seront pas animés, dont nous allons avoir besoin. Dans ces styles, nous positionnons l'objet en absolu, nous lui donnons une taille et un fond de couleur pour le rendre visible.

Un carré est composé de quatre coins, qui seront nos quatre positions, créons le premier point,  première étape de l'animation de notre carré baladeur et appliquons la classe définie à notre div.

#myObject.position1 {left:100px; top:100px;}

(Il est possible de supprimer les valeurs "left" et "top" de la classe définie pour notre objet.)

Puis déclinons la pour imaginer les étapes d'après.

#myObject.position2 {left:200px; top:100px;}
#myObject.position3 {left:200px; top:200px;}
#myObject.position4 {left:100px; top:200px;}

Si vous chargez votre HTML tel quel, vous devriez voir votre carré en position, mais totalement inanimé. Nous devons maintenant animer tout cela avec un script.

Créons un objet JS qui sera notre animateur en chef.

var objectAnimator = {
  position:1,
}

Dedans, nous y avons mis la valeur initiale de la position ou plus précisément de la classe appliquée.

Ajoutons la fonction qui permet d'animer, que j'appelle animate. Cette fonction est simple, elle incrémente la valeur de la position, et retourne à la valeur initiale si l'on dépasse la valeur maximale. Ensuite, elle applique le style défini à notre objet.

var objectAnimator = {
    position:1,     animate:function() {
    if (++this.position == 5) this.position = 1;
    document.getElementById('myObject').className = 'position'+this.position;
  },
}

Il ne manque plus qu'un démarreur qui appellera cette fonction en boucle, complétons notre objet scripté

var objectAnimator = {
    position:1,     start:function() {
    this.loop = setInterval(function(){objectAnimator.animate();}, 1000);   },     animate:function() {
    if (++this.position == 5) this.position = 1;
    document.getElementById('myObject').className = 'position'+this.position;
  },
}

La fonction start défini une boucle sans fin, qui appelle la fonction animate toutes les secondes. Et comme notre transition dure une seconde aussi, l'animation complète sera fluide.

Vous pouvez recharger votre page web et admirer le résultat.

N'oubliez pas que si vous ajoutez une propriété dans l'une des étapes de l'animation, elle reviendra à son état initial avec une transition à l'étape d'après (sauf à faire perdurer la propriété tout au long des différentes étapes).

Ainsi si nous pouvons faire faire à notre carré, un double demi-tour, tel Actarus allant de la soucoupe à la tête de Goldorak

#myObject.position1 {left:100px; top:100px;}
#myObject.position2 {left:200px; top:100px;}
#myObject.position3 {left:200px; top:200px; transform:rotate(180deg);}
#myObject.position4 {left:100px; top:200px;}

Mais il reste toujours un problème que nous n'avons pas réglé. Notre script se lance automatiquement toutes les secondes, mais que ce passe-t-il si nous mettons une transition de 2 secondes, soit deux fois plus longue

#myObject.position1 {left:100px; top:100px;}
#myObject.position2 {left:200px; top:100px;}
#myObject.position3 {left:200px; top:200px; transition:all 2s linear;}
#myObject.position4 {left:100px; top:200px;}

Et oui, l'effet n'est plus du tout le même, c'est complétement foireux et le trajet n'est plus même, mais vous remarquerez que l'animation continue tout de même en reprenant son court dès l'étape suivante.

S'il est possible de ralonger le délai entre deux appels de l'animation, cela ne corrigera que temporairement le problème, et puis l'animation deviendra bien trop longue.

Pour corriger cela, il y a une solution, mais cela sera passera au niveau du script. Attention, cela ne fonctionne cependant que sur des navigateurs modernes, mais désormais assez répendus (Firefox 4, Chrome 26, Safari 6, ...).

Il existe un écouteur qui permet, une fois connecté à l'object, de déclencher des événements et donc d'effectuer des actions.

Pour obtenir le résultat que nous souhaitons il suffit de démarrer une nouvelle étape dès que la transition précédente se termine, nous aurons ainsi de nouveau une animation parfaitement fluide, malgrès des transitions à durée différente.

Pour cela, il faut modifier entièrement la fonction start, et la revoir complétement

var objectAnimator = {
    position:1,     start:function() {
    document.getElementById('myObject').addEventListener('transitionend', function(){objectAnimator.animate();});   },     animate:function() {
    if (++this.position == 5) this.position = 1;
    document.getElementById('myObject').className = 'position'+this.position;
  },
} window.onload = function() {objectAnimator.start();};

Nous ajoutons l'écouteur, et lui demandons de lancer le script animate dès qu'une transition est terminée. Mais pour que l'animation démarre, il faut évidemment activer une première fois animate "à la main" pour lancer la machine.

var objectAnimator = {
    position:1,     start:function() {
    document.getElementById('myObject').addEventListener('transitionend', function(){objectAnimator.animate();});     this.animate();   },     animate:function() {
    if (++this.position == 5) this.position = 1;
    document.getElementById('myObject').className = 'position'+this.position;
  },
} window.onload = function() {objectAnimator.start();};

Et voila, notre animation est parfaitement fonctionnelle, à nouveau. Et comme vous pouvez le voir, l'utilisation d'un écouteur n'est pas si complexe qu'on pourrait le croire.

Mais tout n'est malheureusement pas parfait. Voyons un nouveau problème qui peut se poser à nous.

Faisons évoluer notre animation pour dessiner un losange, voici les nouveaux styles.

#myObject.position1 {left:300px; top:200px;}
#myObject.position2 {left:400px; top:300px;}
#myObject.position3 {left:300px; top:400px;}
#myObject.position4 {left:200px; top:300px;}

Et relancer votre script, vous verrez qu'il aura un comportement erratique. Pourquoi ? Et qu'est ce qui a vraiment changer par rapport à notre premier script ?

Pour faire notre losange, nous faisons des diagonales, c'est à dire que la valeur de top, et la valeur de left changent en même temps, alors que dans notre carré, ne changeait que l'une ou l'autre des deux valeurs. En fait, l'écouteur déclenche l'événement pour chacune des propiétés qui a bougé et qui est arrivée au bout. Attention alors, si vos styles contiennent plusieurs propriétés en transition dont la durée est différente.

Pour régler ce soucis, nous avons plusieurs solutions. La première est de vérifier quelle est la propriété pour ne continuer l'animation que sur un élément.

var objectAnimator = {
    position:1,     start:function() {
    document.getElementById('myObject').addEventListener('transitionend', function(event){objectAnimator.animate(event);});     this.animate();   },     animate:function(event) {
    if (event && event.propertyName == 'top') return;
    if (++this.position == 5) this.position = 1;
    document.getElementById('myObject').className = 'position'+this.position;   },
} window.onload = function() {objectAnimator.start();};

L'autre solution, si les transitions d'une même classe ont la même durée, est de mettre un timer, qui vérifie qu'il n'y a pas deux transitions sur une durée très courte, ici, moins de 3 millisecondes

var objectAnimator = {
    position:1,   lastTransition:0,     start:function() {
    document.getElementById('myObject').addEventListener('transitionend', function(event){objectAnimator.animate(event);});     this.animate();   },     animate:function() {
         d = new Date();     n = d.getSeconds()*1000 + d.getMilliseconds();              diff = n - this.lastTransition;     if (diff < 0) diff += 60000;
    
    if (diff < 3) { console.log(diff); return;}          this.lastTransition = n;     if (++this.position == 5) this.position = 1;
    document.getElementById('myObject').className = 'position'+this.position;
  },
} window.onload = function() {objectAnimator.start();};

A vous d'adapter votre script selon vos besoins.

Pour finir, en bonus, je vous propose de transformer votre losange en cercle. Comment ? Tout simplement, en séparant les transitions, et en utilisant les fonctions d'accéleration.  Sans aller dans les courbes de Bézier, plus précises, les fonctions prédéfinies sont largement suffisantes

#myObject.position1 {left:300px; top:200px; transition:left 2s ease-in, top 2s ease-out;}
#myObject.position2 {left:400px; top:300px; transition:left 2s ease-out, top 2s ease-in;}
#myObject.position3 {left:300px; top:400px; transition:left 2s ease-in, top 2s ease-out;}
#myObject.position4 {left:200px; top:300px; transition:left 2s ease-out, top 2s ease-in;}

Voila, tout ça est très rigolo, mais pas très utile tel quel, c'est maintenant à vous de trouver les applications, pour exploiter ces fonctionnalités.

 
Sommaire de la série
 
 
Commentaires
Aucun commentaire pour le moment.

 

Poster un commentaire
En postant sur skymac.org, je m'engage à être courtois et à ce que mon message soit pertinent avec le sujet de l'article.
En outre, j'accepte, sans condition, que mon message soit refusé et supprimé si ces règles ne sont pas appliquées.
Les cookies assurent le bon fonctionnement de nos services. En continuant, vous acceptez leur utilisation sur notre site internet.
Accepter