Delegate

Timer

Voici un minuteur simple et optimisé qui peut être utilisé sur n'importe quel projet de site Internet : j'expose simplement la logique et explique en détail son fonctionnement.

Le code du minuteur JavaScript est complètement autonome (aucune dépendance à des librairies) et fonctionne sur tous les navigateurs qui supportent ECMAScript 3+ (ES3, 1999).

Le code peut aisément être adapté à toute librairie à des fins d'optimisation et d'uniformisation. Le poids du fichier est négligeable et varie entre 500 octets et 2 ko selon la technique de minimisation.

Code source


/*
* La fonction « TimerEngine » sera instanciée, on peut
* donc la considérer comme une classe (OOP).
*
* Ex: var myTimer = new TimerEngine( ... );
* Voir le bloc « utilisation » après le code source du minuteur.
*/
 
function TimerEngine( end_date , callback ){
	/*
	* end_date:date
	* callback:function
	*/
	 
	// On s'assure que le paramètre « end_date » est bien un objet Date.
	var _end_date = Object.prototype.toString.call( end_date ) === '[object Date]' ? end_date : new Date() ;
	 
	// On s'assure que le paramètre « callback » est bien une fonction.
	var _callback = Object.prototype.toString.call( callback ) === '[object Function]' ? callback : null ;
	 
	/*
	* On assigne la portée de l'instance à une variable afin de l'utiliser
	* lors des appels de fonction publique interne. (Il est possible que la 
	* portée d'une fonction publique soit modifiée selon le contexte d'exécution
	* et nous voulons simplement éviter ce type d'erreur)
	*/
	var _scope = this ;
	 
	/*
	* On définit l'intervalle de temps entre chaque exécution (millisecondes).
	* Puisqu'on calcule le temps écoulé, modifier cette valeur
	* n'affecte que la fréquence du calcul et la précision du rendu.
	*/
	var _interval = 1000 ;
 
	// on définit une variable qui sera la référence à l'intervalle afin de pouvoir l'arrêter
	var _interval_id ;
 
	/*
	* Une méthode privée à la classe qui sert à ajouter des zéros
	* afin d'uniformiser les nombres lors de l'affichage.
	*/
	function pad( input , limit ){
		/*
		* input:number
		* limit:number
		*/
 
		// On transforme le paramètre « input » en chaîne de caractères
		var input = input + '' ;
 
		// On s'assure que le paramètre « limit » est bien un nombre
		var limit = typeof limit == 'number' ? limit : 1 ;
 
		/*
		* Si la chaîne de caractères est de la bonne longueur, on la retourne.
		* Sinon, on créer un tableau de la longueur désirée, puis on ajoute 
		* des zéros entre les éléments du tableau lors de la conversion
		* en chaîne de caractères.
		*/
		return input.length >= limit ? input : new Array( limit - input.length + 1 ).join( '0' ) + input ;
	}
 
	/*
	* Une méthode privée à la classe qui sert à calculer le temps écoulé. Les
	* résultats sont envoyés à la fonction « callback », externe à la classe, utilisée
	* pour gérer le rendu HTML.
	*/
	function calculate(){
 
		/*
		* On calcule la différence de temps, en millisecondes, entre
		* la date initiale et le temps présent.
		*/
		var difference = _end_date.getTime() - new Date().getTime() ;
 
		/*
		* On créer un objet « data » afin de structurer nos données puis on
		* convertit les millisecondes en différentes unités de temps selon
		* le schéma suivant:
		*  num: le nombre calculé
		*  pad: le nombre calculé uniformisé avec le nombre de zéros désiré
		*/
		var data = {
			days : {
				num : difference / ( 1000 * 60 * 60 * 24 ) ,
				pad : 0
			} ,
			hours : {
				num : ( difference / ( 1000 * 60 * 60 ) ) % 24 ,
				pad : 0
			} ,
			minutes : {
				num : ( ( difference / 1000 ) / 60 ) % 60 ,
				pad : 0
			},
			seconds : {
				num : ( difference / 1000 ) % 60 ,
				pad : 0
			}
		} ;
 
		/*
		* Afin de minimiser le code, on créer une boucle
		* pour formater les données de l'objet.
		*/
		for( var i in data ){
 
			/*
			* La variable « num » peut contenir plusieurs nombres dans la partie
			* décimale, nous allons donc l'arrondir à l'entier inférieur.
			*  Ex: 1.9999072800925926 = 1
			* On s'assure également que le chiffre soit toujours supérieur ou égal à 0
			*/
			data[ i ].num = Math.max( Math.floor( data[ i ].num ) , 0 ) ;
 
			/*
			* La chaîne de caractères « pad » est simplement le chiffre calculé
			* et arrondi « num » mais uniformisé avec le nombre de zéros désiré.
			*/
			data[ i ].pad = pad( data[ i ].num , 2 ) ;
		}
 
		/*
		* On créer un booléen qui aura un état « vrai » si tout
		* les valeurs « num » sont à zéro, ce qui indique que le 
		* minuteur est terminé.
		*/
		data.completed = data.days.num == 0 && data.hours.num == 0 && data.minutes.num == 0 && data.seconds.num == 0 ;
 
		/*
		* Si la fonction « callback » est définie, on fait un appel
		* en passant l'objet « data » en paramètre.
		*/
		if( _callback != null ) _callback( data ) ;
 
		/*
		* Si le minuteur est terminé, on arrête les
		* exécutions ultérieures de la fonction « calculate »
		* pour limiter l'utilisation des ressources de l'appareil.
		*/
		if( data.completed ) _scope.stop() ;
	};
 
	// Une méthode publique qui sert à démarrer le minuteur
	_scope.start = function(){
		// On appelle la fonction « stop » afin de réinitialiser le minuteur.
		_scope.stop();
 
		/*
		* On créer un intervalle d'exécution et assigne
		* l'identifiant généré à la variable.
		*/
		_interval_id = setInterval( calculate , _interval ) ;
 
		/*
		* Puisque l'intervalle de calcul est de 1 seconde, on appelle
		* la fonction « calculate » pour exécuter le premier rendu
		* afin qu'il n'y ait pas de délai.
		*/
		calculate();
	};
 
	// Une méthode publique qui sert à arrêter le minuteur 
	_scope.stop = function(){
		clearInterval( _interval_id );
	};
 
};

Code source sans commentaire


function TimerEngine(end_date,callback){
	var _end_date=Object.prototype.toString.call(end_date)==='[object Date]'?end_date:new Date(),
	_callback=Object.prototype.toString.call(callback)==='[object Function]'?callback:null,
	_scope=this,_interval=1000,_interval_id;		
	function pad(input,limit){
		var input=input+'',limit=typeof limit=='number'?limit:1;
		return input.length>=limit?input:new Array(limit-input.length+1).join('0')+input;
	};
	function calculate(){
		var difference=_end_date.getTime()-new Date().getTime(),
		data={
			days:{num:difference/(1000*60*60*24),pad:0},
			hours:{num:(difference/(1000*60*60))%24,pad:0},
			minutes:{num:((difference/1000)/60)%60,pad:0},
			seconds:{num:(difference/1000)%60,pad:0}
		};
		for(var i in data){
			data[i].num=Math.max(Math.floor(data[i].num),0);
			data[i].pad=pad(data[i].num,2);
		}
		data.completed=data.days.num==0&&data.hours.num==0&&data.minutes.num==0&&data.seconds.num==0;
		if(_callback!=null)_callback(data);
		if(data.completed)_scope.stop();			
	};
	_scope.start=function(){
		_scope.stop();
		_interval_id=setInterval(calculate,_interval);
		calculate();			
	};
	_scope.stop=function(){
		clearInterval(_interval_id);
	};
};

Utilisation


/*
* On instancie la classe TimerEngine et assigne
* l'instance à la variable « myTimer ».
* 
* On change la date de fin selon nos besoins.
*  new Date(années,mois,jours,heures,minutes,secondes)
* 
* C'est dans la fonction anonyme que sera gérée la vue
* du minuteur et on est libre de l'adapter selon nos
* goûts et besoins du projet.
* 
*/
var myTimer=new TimerEngine(
	new Date(2026,12,24,23,0,0),
	function(data){
		// data:object

		// On sélectionne l'élément ayant l'identifiant « demo ».
		var element=document.getElementById('demo');
 
		if(element!=null){
			// On prépare puis on change le contenu de l'élément.
			var content='';
			content+='<span class="d">'+data.days.pad+'</span>';
			content+='<span class="h">'+data.hours.pad+'</span>';
			content+='<span class="m">'+data.minutes.pad+'</span>';
			content+='<span class="s">'+data.seconds.pad+'</span>';
			element.innerHTML=content;
		}
 
		/*
		* Lorsque le minuteur est terminé, le booléen « data.completed » aura
		* la valeur « vrai ». On peut donc démarrer une animation, changer
		* le style, afficher un message, etc.
		* 
		* À noter que le minuteur est déjà arrêté lorsque cette condition
		* devient « vrai » donc, nul besoin de faire un appel à la
		* fonction « myTimer.stop(); ».
		*/
		if(data.completed){
			// le décompte est terminé
		}
	}
);
 
// On démarre le minuteur
myTimer.start();
//myTimer.stop();