
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.
/*
* 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 );
};
};
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);
};
};
/*
* 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();