#GoTWar les animations en JS

Concepts et développements javascript du système d'animation de la #GoTwar

Cette page n'est considérée comme plus valide. Les informations qu'elle peut contenir peuvent être obsolètes.

La GoTwar ?

Cet article fait suite au développement de la GoTwar, un dispositif web assurant la promotion de la saison 5 de Game of Thrones. Tous les détails de ce projet ici : La GoTwar.

gotwar




Le principe

Durant l’opération GoTwar, les participants pouvaient consulter l’impact de leurs tweets sur la carte de la bataille. L’interface web affichait les interactions entre chaque armée avec des animations. Il y avait plusieurs types d’animations :

  • Déplacements de soldats (gains ou pertes)
  • Affichages d’infobulle
  • Messages de notification

Ces animations étaient en quasi temps réel. Nous avions un léger décalage afin de créer le scénario d'animations pour les 5 minutes à venir (basé sur les interactions Twitter des 5 minutes précédentes).

Le principe était d’avoir un tableau d’évènements combiné à des timers (des minuteurs qui feront passer d'un évènement à un autre) pour organiser correctement leurs affichages. Pour le prochain exemple, nous allons seulement nous concentrer sur le cas de déplacements de soldats. Le but de cette animation est d’afficher une colonne de lumière avec l’affichage d’un nombre représentant la différence négative ou positive de soldats.


Animation d'une colonne de lumière



Le code

Cette partie du site était développée essentiellement en javascript. Il n'y aura donc aucun code php dans cet article. Pour commencer, il faut récupérer la liste des évènements pour les traiter. Pour cela, une simple fonction dans notre fichier javascript.


$(document).ready(function() {

	// Initialisation des variables dont nous aurons besoin
	var $timerAnimationDeplacements = 0,
		$timerColonne = 0,
		$durationColonne = 0,
		$eventsDeplacements = [];

	/**
	 * Récupération des évènements du flux twitter
	 * @return void
	 */
	function getEvents() {
		// Appel ajax pour la récupération des évènements
		// url correspond à l'adresse url interne au projet qui retournera un tableau
		// d'évènements JSON
		$.get(url, function(data) {
			// On parse les données pour avoir un format JSON correct
			data = $.parseJSON(data);
			// On initialise le nombre de déplacement
			var dataDeplacements = 0;

			// On incrémente le nombre de déplacement à chaque fois 
			// que l'on en identifie un dans la liste des évènements
			$.each(data, function(index, val) {
				if (val.type == 'deplacement') {
					dataDeplacements++;
				}
			});

			// On calcule ensuite un intervalle pour le nombre de 
			// déplacement sur 5 minutes (temps avant de récupérer d'autres évènements)
			var intervalDeplacements = (5*60) / dataDeplacements;

			// On insère chaque déplacement dans un tableau avec ses caractéristiques 
			// et son intervalle de temps
			$.each(data, function(index, val) {
				if (val.type == 'deplacement') {
					$eventsDeplacements.push({
						'event': val, 
						'interval': intervalDeplacements
					});
				}
			});

			// Si aucun timer n'est en cours
			if ($timerAnimationDeplacements === 0) {
				// On lance le timer pour le premier évènement de la liste
				__startTimerDeplacements($eventsDeplacements[0].interval);
			}
		});
	}
});

Une fois les déplacements récupérés, il faut configurer le timer pour lancer ces animations, espacées de l’intervalle calculé.


/**
 * Arrêt du timer
 * @return void
 */
function __stopTimerDeplacements() {
	clearInterval($timerAnimationDeplacements);
	$timerAnimationDeplacements = 0;
}

/**
 * Initialisation du timer
 * @param  int duration Durée de l'intervalle de temps
 * @return void
 */
function __startTimerDeplacements(duration) {
	$timerAnimationDeplacements = setInterval(function() {

		// On stoppe le timer par sécurité
		__stopTimerDeplacements();
		
		// Pour le premier évènement de la liste, on affiche la colonne de lumière
		// On configure la durée de la colonne de lumière par 
		// 2/3 de la durée totale de l'évènement,
		// que l'on multiplie par 1000 pour la conversion en secondes
		__showColumn($eventsDeplacements[0].event, Math.round(duration * (2/3) * 1000));

		// On supprime ensuite le premier évènement de la liste
		$eventsDeplacements.shift();

		// S'il y a encore des évènements à traiter,
		// on lance le nouveau timer avec l'intervalle de temps du prochain évènement
		if ($eventsDeplacements.length > 0) {
			__startTimerDeplacements($eventsDeplacements[0].interval);
		}

	}, Math.round(duration * 1000));
}

Le fait de séparer le code en différentes fonctions permet une meilleure modularité, lisibilité et réutilisabilité. Cela est aussi très utile pour les tests de nos fonctions et pour identifier les éventuels bugs.

Une fois que les timers sont prêts, il ne nous reste plus qu’à procéder à l’affichage des colonnes de lumière. Pour cela, créons les dernières fonctions manquantes.


/**
 * Suppression de la colonne
 * @return void
 */
function __hideColumn() {
	// Changement de l'opacité pour la rendre invisible
	$('.lightColumn').velocity(
		{opacity: 0}, 
		{ 
			duration: 200,
			complete: function() {
				// Suppression de l'élément HTML
				$('.lightColumn').remove();
				// Réinitialisation du timer
				clearTimeout($timerColonne);
				$timerColonne = 0;
			}
		}
	);
}

/**
 * Affichage d'une colonne de lumière
 * @param  object e  		Evènement
 * @param  int       duration   Durée de l'animation
 * @return void
 */
function __showColumn(e, duration) {
	// Calcule des coordonnées x et y de la colonne
	var posX = e.datas.x,
		posY = e.datas.y,
		// Récupération des coordonnées de la case concernée
		$coordonnees = $('.case[data-x=' + posX + '][data-y=' + posY + ']').position(),
		// Initialisation de l'opérateur (+ ou -)
		$operateur = '',
		// Sélecteur pour l'élément HTML de la colonne de lumière
		$selectorColumn = 'lightColumn';

	// Si c'est dans un cas d'ajout de soldat
	if (e.datas.type == 'plus') {
		// Le type est positif alors l'opérateur est "+"
		$operateur = '+';
	} else {
		// Sinon il est "-"
		$operateur = '-';
	}

	// Ajout sur la carte de l'élément HTML pour la colonne
	// Avec comme texte le nombre de soldats gagnés ou perdus
	$('#carte').prepend(
		'<div class="' + 
		$selectorColumn + 
		'"><div class="-texte"><p>' + 
		$operateur + Math.abs(e.datas.reels) + 
		'>/p><p<soldats>/p></div></div<'
	);

	// Positionnement du côté gauche de la colonne
	// Le calcul mathématique est spécifique au projet,
	// pour centrer correctement la colonne au centre de la case
	$('.' + $selectorColumn).css(
		'left', ($coordonnees.left - ((155 - (50 / 1.5)) / 2)) + 'px'
	);

	// Animation du texte de la colonne
	$('.' + $selectorColumn + ' .-texte').velocity({
		bottom: '200px', opacity: 0
	}, {
		easing: "easeInSine", 
		duration: 1500
	});

	// Vérification de la durée d'animation de la colonne
	// Son maximum est d'une seconde
	$durationColonne = (duration > 1000) ? 1000 : duration;

	// Animation de la colonne 
	// Sa hauteur augmente jusqu'à atteindre le milieu de la case
	// Et son opacité augmente progressivement pour qu'elle soit visible.
	// Le calcul mathématique est spécifique au projet,
	// pour centrer correctement la colonne au centre de la case.
	$('.' + $selectorColumn).velocity(
		{ 
			height: ($coordonnees.top + (50 / (1.5 * 2))), 
			opacity: 1 
		}, 
		{ 
			easing: "easeInOutSine",
			duration: 300, 
			complete: function() {
				// Création du timer pour la colonne de lumière
				$timerColonne = setTimeout(function() {
					// Une fois le temps écoulé,
					// suppression de la colonne de lumière
					__hideColumn();
				}, $durationColonne);
			}
		}
	);
}

Pour gérer ces animations nous avons utilisé la librairie Velocity.js. Elle est plus rapide que la fonction animate() de jQuery et permet d’avoir de meilleurs rendus. Velocity garde la même structure que la fonction animate() mais ajoute le support des transitions CSS, d'animations des couleurs, du SVG, scroll, etc. Elle exécute en priorité les transitions CSS3 pour des performances optimales.


Récapitulatif

Nous avons réalisé les animations pour les déplacements de soldats de la GoTwar. Pour cela, nous avons donc :

  • Géré une liste d’évènements
  • Configuré un timer
  • Animé une colonne de lumière

Mis à part l’utilisation de Velocity.js pour les animations, nous n’avons pas eu besoin d’utiliser de librairies externes (si l’on exclut jQuery bien sûr). C’est un avantage, car sur un projet doté de beaucoup d’animations, il fallait alléger les ressources le plus possible. Le découpage de ce code en plusieurs fonctions nous permet de le réutiliser pour n’importe quel projet et d'accélérer le processus de développement.


Cet article est terminé. Nous vous invitons à consulter un article précédent qui présente la récupération et traitements de tweets grâce à des robots pour la GoTwar.

En tous cas, n'hésitez pas à laisser votre avis et à poser des questions dans la zone de commentaires ci-dessous.