Contatore alla rovescia su più elementi simultaneamente

the_indios

Nuovo Utente
29 Gen 2020
13
0
1
Salve a tutti

ho un elenco di secondi rimanenti di attività prelevati dal database. Sono riuscito ad impostare un timer solo per un oggetto con questo codice
JavaScript:
for (var i=0; i < (arr.length); i++){

 

            var seconds = arr[i].deltaSec;

                function timer() {



                var days        = Math.floor(seconds/24/60/60);

                var hoursLeft   = Math.floor((seconds) - (days*86400));

                var hours       = Math.floor(hoursLeft/3600);

                var minutesLeft = Math.floor((hoursLeft) - (hours*3600));

                var minutes     = Math.floor(minutesLeft/60);

                var remainingSeconds = seconds % 60;

                if (remainingSeconds < 10) {

                    remainingSeconds = "0" + remainingSeconds;

                }


                id = 'countDown'+(i-1);


                document.getElementById(id).innerHTML = days + "d " + hours + "h " + minutes + "m " + remainingSeconds+ "s";

                if (seconds == 0) {

                    clearInterval(countdownTimer);

                    document.getElementById(id).innerHTML = "Completed";

                } else {

                    seconds--;

                }

            }

            var countdownTimer = setInterval("timer()", 1000);

        }
con il for stavo cercando di farlo andare su tutti gli oggetti della base di dati, ma non ci sono riuscito.

Probabilmente dovrei usare l'elaborazione asincrona o la concorrenza.

Potreste darmi una mano spiegandomi come devo fare?

Grazie in anticipo
 
Ultima modifica di un moderatore:

Max 1

Super Moderatore
Membro dello Staff
SUPER MOD
MOD
29 Feb 2012
4.069
299
83
@the_indios
Da regolamento del forum, come tutti noi sei tenuto ad usare il tag apposito quando posti del codice, oppure la funzione codice dalla barra degli strumenti Non delle immagini

Inoltre IMPORTANTE: Prima di creare una nuova discussione o di rispondere alle discussioni esistenti ricordati di leggere attentamente il Regolamento del Forum e l'eventuale regolamento specifico della sezione!
Correggi il tuo post o devo chiudere la discussione
Grazie
 

Max 1

Super Moderatore
Membro dello Staff
SUPER MOD
MOD
29 Feb 2012
4.069
299
83
Ti ho modificato io il tag del codice con il tag per il JS
 

WmbertSea

Utente Attivo
28 Nov 2014
159
21
28
Salve, ci sono diversi aspetti e problemi di fondo da chiarire per poter capire come procedere ed ottenere ciò che stai cercando di fare.

Cerco di riassumere il più possibile per dare una panoramica. Posto poi un esempio funzionante che ho elaborato partendo dalle informazioni del tuo script; chiaramente potrai prenderlo come riferimento e adoperarlo/svilupparlo come ti pare.

Probabilmente dovrei usare l'elaborazione asincrona o la concorrenza.
Faccio una premessa molto a grandi linee:

Il meccanismo di "concorrenza" in javascript è ben diverso da quello usato in altri tipi di linguaggi multi-threaded.

Qui tutto avviene in un unico thread che gestisce l'intera coda di esecuzione delle varie azioni determinate dagli eventi.
Nessuna azione può essere interrotta per dare spazio all'esecuzione di altre azioni determinate da altri eventi.
Nessuna azione può essere eseguita contemporaneamente con altre ma la coda viene eseguita dal runtime javascript in maniera sequenziale e senza interruzioni.

Di tutto questo bisogna tenerne conto quando si sviluppa con questo linguaggio, soprattutto quando si ha a che fare con la gestione di eventi legati in modo particolare al tempo, come nel tuo caso.

1) Creare un timer preciso

Da qui nasce un primo problema: in javascript un timer (impostato con setInterval o setTimeout) non potrà mai garantire la precisione del tempo che è stato impostato come intervallo dello stesso, perché il tempo di esecuzione della funzione callback è soggetto ai ritardi dovuti dall'esecuzione dello script stesso e delle azioni che stanno davanti alla coda prima di lui. Oltre questo, molti altri fattori possono incidere su questi ritardi.

Se vuoi avere una certa garanzia che il tempo mostrato nel tuo timer sia veritiero, per prima cosa devi trovare un sistema più preciso che non affidarti al semplice tempo impostato per l'intervallo.

In casi del genere solitamente ci si basa sull'orologio del proprio sistema operativo. Puoi popolare una variabile con un timestamp prima dell'avvio del timer quindi, per ogni iterazione, vai a confrontare il timestamp iniziale con quello corrente. La differenza di questi valori ti restituisce un delta preciso che puoi utilizzare per le altre operazioni e per il refresh degli elementi a video.

Qui una discussione su stackoverflow riguardo questo argomento:

2) Ottimizzare l'esecuzione di istanze multiple usando un unico timer

NON hai bisogno di creare istanze multiple di un timer se il tempo di intervallo deve essere comunque lo stesso per tutte le istanze. Conviene invece utilizzare un unico timer in cui vai a ciclare gli elementi che devono essere aggiornati.

Questa impostazione riguarda chiaramente l'architettura dello script ma è comunque inerente al fatto di avere sempre quell'unico thread con un'unica coda. Anche avendo più istanze del timer, i relativi callback e gli script contenuti non potranno comunque essere eseguiti contemporaneamente. In tal caso il calcolo del tempo per scandire ogni secondo di ogni countdown, tenendo conto del timestamp corrente, dovrebbe essere effettuato singolarmente per ogni istanza con la conseguenza che quel timestamp risulterebbe inevitabilmente differente (anche se nell'ordine di millisecondi) da un'istanza all'altra, mentre dovrebbe comunque essere uguale.

Una soluzione più ottimale quindi sta nell'usare un solo timer in cui inserire il calcolo del timestamp corrente, che sarà uguale per tutti i countdown e che sarà usato poi all'interno di un ciclo (inserito nel callback del timer stesso) per calcolare e aggiornare il tempo corrente di ogni elemento countdown.

Mi sono un po' attorcigliato nel discorso ma forse questa discussione su stackoverflow può chiarire meglio il concetto:

3) Occhio allo scope quando dichiari variabili e funzioni

A prescindere dai punti precedenti, che già offrono una risoluzione generale del problema in oggetto, il tuo script ha comunque delle pecche rilevanti a livello di programmazione javascript.

Lo scope in javascript, quando si usa var o function per dichiarare rispettivamente variabili e funzioni, in generale NON è definito a livello di blocco di codice tra graffe, ma bensì a livello di funzione (o del documento globale).

Supponendo che il tuo script sia a livello globale, quelle variabili dichiarate con "var seconds" e "var countdownTimer" e quella funzione dichiarata con "function timer" NON hanno visibilità locale dentro il ciclo for (cioè dentro il blocco di codice tra graffe) ma bensì sono tutte visibili a livello globale, fuori da quel ciclo.

Questo significa che countdownTimer e timer(), in modo particolare, vengono sovrascritte di volta in volta per ogni iterazione di quel ciclo, perdendo di fatto quella caratteristica di "istanziazione" che stavi cercando di ottenere.

Un altro errore, sempre legato a questo problema, è quello di far riferimento, da dentro una funzione, a delle variabili con valori "scaduti", usate dentro il ciclo che sta fuori dalla funzione. Nel tuo script, dentro la funzione timer() (e quindi per ogni istanza di tale funzione) hai fatto riferimento alla variabile "i", che hai usato come contatore nel ciclo for (fuori dalla funzione), aspettandoti che questa variabile restituisca i relativi valori acquisiti durante le iterazioni del ciclo. Qui però il problema è sempre lo stesso, quella variabile è dichiarata a livello globale e non nel contesto del ciclo, quindi il valore che otterrai sarà sempre quello determinato dall’ultimo passaggio del ciclo, perché è quello che rimane in memoria per tale variabile nello scope globale.

In questa circostanza si potrebbe intervenire in alcuni modi per risolvere, ad esempio dichiarare variabili e funzione tramite let e const (introdotti in ES6) in modo che facciano riferimento allo scope definito a livello di blocco, oppure utilizzare una function expression per racchiudere quella parte di script dentro un opportuno scope locale.

Non mi dilungo comunque su questo punto ma se tu vuoi approfondire puoi fare qualche ricerca.

A seguire posto l'esempio.
 

WmbertSea

Utente Attivo
28 Nov 2014
159
21
28
Qui l'esempio, ho commentato le varie righe perché si capisca il procedimento che ho usato ma se hai bisogno di ulteriori chiarimenti non esitare a chiedere.
HTML:
<!doctype html>
<html lang="en">
<head>
  <title>Countdown - istanze multiple</title>
  <meta charset="utf-8">
  <style>
      .countdown-element{
         width: 200px;
         margin: 5px;
         padding: 5px;
         background: #eee;
      }
      .countdown-element.countdown-runningout{
         background: #fa0;
      }
      .countdown-element.countdown-completed{
         background: #f50;
         color: #fff;
      }
      
  </style>
</head>
<body>
  
   <div class="countdown-element" id="countDown0"></div>
   <div class="countdown-element" id="countDown1"></div>
   <div class="countdown-element" id="countDown2"></div>
   <div class="countdown-element" id="countDown3"></div>
   <div class="countdown-element" id="countDown4"></div>
   <div class="countdown-element" id="countDown5"></div>
   <div class="countdown-element" id="countDown6"></div>
   <div class="countdown-element" id="countDown7"></div>
  
   <script>
  
                                                               // Array dei dati
   var arr = [
      {deltaSec: 5},
      {deltaSec: 300},
      {deltaSec: 80000},
      {deltaSec: 3600},
      {deltaSec: 30},
      {deltaSec: 45},
      {deltaSec: 15},
      {deltaSec: 80}
   ];
  
                                                               // Array per la coda degli elementi gestiti nell'intervallo
   var countdownQueue = [];
  
                                                               // Creo la coda aggiungendo gli elementi interessati e alcune proprietà per facilitare il procedimento
   for (let i=0; i < (arr.length); i++){
      let element = document.getElementById('countDown'+i);
      countdownQueue[i] = {
         element: element,
         initSeconds: arr[i].deltaSec,
         curSeconds: arr[i].deltaSec
      };
                                                               // Visualizzo tutti gli elementi allo stato iniziale prima di avviare il countdown
      refreshElement(countdownQueue[i])
   }
  
                                                               // Timestamp di partenza basato sull'orologio locale di sistema
                                                               // NOTA: Il setInterval non garantisce la regolarità del tempo impostato come intervallo.
                                                               //       Vari fattori possono influire sull'esecuzione generando, ad ogni iterazione,
                                                               //       un ritardo sempre maggiore rispetto al reale tempo.
                                                               //       Per ottenere un risultato più preciso è preferibile eseguire il calcolo ad ogni iterazione
                                                               //       considerando il reale tempo trascorso dall'avvio dello script
   var initTime = Date.now();
  
                                                               // Avvio il countdown
                                                               // NOTA: Data l'imprecisione del setInterval, soggetto a ritardi, può essere utile
                                                               //       impostare, per l'intervallo, un valore sensibilmente minore di un secondo.
                                                               //       Questo previene/limita gli errori di aggiornamento a video in cui potrebbero
                                                               //       avvenire scatti maggiori del normale secondo, dovuti appunto all'accumulo dei ritardi.
   var countdown = setInterval(stepInterval, 500);
  
                                                               // Callback intervallo
   function stepInterval() {
                                                               // Tempo effettivo trascorso dall'avvio
      var curDeltaSec = Math.floor((Date.now() - initTime) / 1000);
      
                                                               // Ciclo gli elementi in coda
                                                               // NOTA: Uso il metodo filter per rielabora la coda eliminando di volta in volta gli elementi completati
      countdownQueue = countdownQueue.filter(function(obj, index) {
                                                               // Aggiorno i secondi attuali di questo elemento, sottraendo quelli realmente trascorsi
         obj.curSeconds = Math.max(0, obj.initSeconds - curDeltaSec);
                                                               // Visualizzo il risultato
         refreshElement(obj);
                                                               // Mantengo questo elemento nella coda se non è completato
         return (obj.curSeconds !== 0)
      });
                                                               // Se la coda è vuota rimuovo l'intervallo
      if (countdownQueue.length == 0) clearInterval(countdown);
      
   }
  
                                                               // Visualizzazione dell'elemento aggiornato
   function refreshElement(obj) {
      var element       = obj.element;
      var seconds       = obj.curSeconds;
      var days          = Math.floor(seconds/24/60/60);
      var hoursLeft     = Math.floor((seconds) - (days*86400));
      var hours         = Math.floor(hoursLeft/3600);
      var minutesLeft   = Math.floor((hoursLeft) - (hours*3600));
      var minutes       = Math.floor(minutesLeft/60);
      var secondsLeft   = ('' + (seconds % 60)).padStart(2, '0');
      
                                                               // Se i secondi sono zero, visualizzo il messaggio di fine countdown
      if (seconds == 0) {
         element.innerHTML = 'Completed';
                                                               // Applico una nuova classe, così posso personalizzare l'elemento da css
         element.classList.add('countdown-completed');
         element.classList.remove('countdown-runningout');
      } else {
                                                               // Altrimenti visualizzo i dati del countdown
         element.innerHTML = days + 'd ' + hours + 'h ' + minutes + 'm ' + secondsLeft+ 's';
        
                                                               // Quando arriva a 10 applico una classe, così posso personalizzare l'elemento da css
         if (seconds <= 10) element.classList.add('countdown-runningout');
      }
   }
  
   </script>
</body>
</html>