2011-01-03 5 views
0

в related question Я разместил этот код. Он почти работает, но счетчик не делает.Разбитое закрытие - пожалуйста, помогите мне исправить его

Можем ли мы исправить это? (Без JQuery, пожалуйста)

<script type="text/javascript"> 
var intervals = []; 
var counters = { 
    "shoes":{"id":"shoe1","minutes":1,"seconds":5}, 
    "trousers":{"id":"trouser1","minutes":10,"seconds":0} 
}; // generate this on the server and note there is no comma after the last item 
window.onload = function() { 
    for (var el in counters) { countdown(counters[el]) }; 
} 

function countdown(element) { 
    intervals[element.id] = setInterval(function() { 
     var el = document.getElementById(element.id); 
     var minutes = element.minutes; 
     var seconds = element.seconds; 
     if(seconds == 0) { 
      if(minutes == 0) { 
       el.innerHTML = "countdown's over!";      
       clearInterval(intervals[element.id]); 
       return; 
      } else { 
       minutes--; 
       seconds = 60; 
      } 
     } 
     if(minutes > 0) { 
      var minute_text = minutes + (minutes > 1 ? ' minutes' : ' minute'); 
     } else { 
      var minute_text = ''; 
     } 
     var second_text = seconds > 1 ? 'seconds' : 'second'; 
     el.innerHTML = minute_text + ' ' + seconds + ' ' + second_text + ' remaining'; 
     seconds--; 
    }, 1000); 
} 
</script> 
shoes: <span id="shoe1"></span><br /> 
trousers: <span id="trouser1"></span><br /> 
+0

«Это не работает» не очень описательно. Можете ли вы рассказать о том, что есть и что не работает? –

+0

Загрузите его и посмотрите, не обновляя время. Как и Джон сделал :) – mplungjan

+1

Просто комментарий. Вы должны использовать 'var interval = {};' вместо 'var interval = [];', потому что вы используете его как словарь, а не массив. – Thai

ответ

4

Вам просто нужно взять объявления переменных minutes и seconds из внутренней функции, например:

function countdown(element) { 
    var minutes = element.minutes; 
    var seconds = element.seconds; 

    intervals[element.id] = setInterval(function() { 
     var el = document.getElementById(element.id); 
     if(seconds == 0) { 
      if(minutes == 0) { 
       el.innerHTML = "countdown's over!";      
       clearInterval(intervals[element.id]); 
       return; 
      } else { 
       minutes--; 
       seconds = 60; 
      } 
     } 
     if(minutes > 0) { 
      var minute_text = minutes + (minutes > 1 ? ' minutes' : ' minute'); 
     } else { 
      var minute_text = ''; 
     } 
     var second_text = seconds > 1 ? 'seconds' : 'second'; 
     el.innerHTML = minute_text + ' ' + seconds + ' ' + second_text + ' remaining'; 
     seconds--; 
    }, 1000); 
} 

Когда вы звоните countdown, вы хотите получать начальные значения для каждого отсчета времени, а затем медленно снижаться (закрытие гарантирует, что значения останутся доступными, если это требует анонимная функция). То, что вы делали раньше, было сброшено значения обратного отсчета в начале каждого тика, поэтому обратный отсчет никогда не имел шансов ... ну, обратный отсчет.

Update:

Если вам необходимо обновить значения внутри window.counters в то время как отсчет активны (хотя я не понимаю, почему вы хотели бы сделать это, если вы хотите сделать что-нибудь значимое с «текущие» значения обратного отсчета, просто сделать это внутри анонимной функции), вы можете просто добавить это в конце:

var second_text = seconds > 1 ? 'seconds' : 'second'; 
el.innerHTML = minute_text + ' ' + seconds + ' ' + second_text + ' remaining'; 
seconds--; 

// ADD THIS: 
element.minutes = minutes; 
element.seconds = seconds; 
+0

ОЧЕНЬ близко к закрытию grokking сейчас :) Спасибо! – mplungjan

+0

Это работает, но не обновляет исходные переменные-члены. –

+0

В этом случае это нормально, я бы подумал. Если значения членов выживут при перезагрузке, тогда это может быть важно, если нет, а не. – mplungjan

4

Пожалуйста, проверьте это

<html> 
    <body> 
     <script type="text/javascript"> 
     var intervals = []; 
     var counters = { 
      "shoes":{"id":"shoe1","minutes":1,"seconds":5}, 
      "trousers":{"id":"trouser1","minutes":10,"seconds":0} 
     }; // generate this on the server and note there is no comma after the last item 
     window.onload = function() { 
      for (var el in counters) { countdown(counters[el]) }; 
     } 

     function countdown(element) { 
      intervals[element.id] = setInterval(function() { 
       var el = document.getElementById(element.id); 
       var minutes = element.minutes; 
       var seconds = element.seconds; 

       if(seconds == 0) { 
        if(minutes == 0) { 
         el.innerHTML = "countdown's over!";      
         clearInterval(intervals[element.id]); 
         return; 
        } else { 
         minutes--; 
         seconds = 60; 
        } 
       } 
       if(minutes > 0) { 
        var minute_text = minutes + (minutes > 1 ? ' minutes' : ' minute'); 
       } else { 
        var minute_text = ''; 
       } 
       var second_text = seconds > 1 ? 'seconds' : 'second'; 
       el.innerHTML = minute_text + ' ' + seconds + ' ' + second_text + ' remaining'; 
       seconds--; 

       element.seconds = seconds; 
       element.minutes = minutes; 
      }, 1000); 
     } 
     </script> 
     shoes: <span id="shoe1"></span><br /> 
     trousers: <span id="trouser1"></span><br /> 
    </body> 
</html> 

Рабочий раствор here.

EDIT: Существовал незначительная проблема со сценарием. Я исправил это.

После пересчета seconds и minutes вы должны установить новые значения в объекте element.

+0

Спасибо - я попробовал что-то подобное, но Джон - это то, что я хотел, так как он более изящный. – mplungjan

+0

И теперь он делает оба :) – mplungjan

+0

Да, это правильный путь –

0

Я думаю, что бы сделать ваш код намного чище и сэкономить несколько if сек, если вы будет сохранять тайм-аут в коде как секунды, а не минуты a й секунда:

var intervals = []; 
var counters = { 
    "shoes":{"id":"shoe1","seconds":65}, 
    "trousers":{"id":"trouser1","seconds":600} 
}; // generate this on the server and note there is no comma after the last item 

window.onload = function() { 
    for (var el in counters) { countdown(counters[el]) }; 
} 

function countdown(element) { 
    intervals[element.id] = setInterval(function() { 
     var el = document.getElementById(element.id); 

     if(element.seconds == 0) { 
      el.innerHTML = "countdown's over!";      
      clearInterval(intervals[element.id]); 
      return; 
     } 

     var minutes = (element.seconds - (element.seconds % 60))/60; 
     if(minutes > 0) { 
      var minute_text = minutes + (minutes > 1 ? ' minutes' : ' minute'); 
     } else { 
      var minute_text = ''; 
     } 

     var second_text = (element.seconds%60) > 1 ? 'seconds' : 'second'; 
     el.innerHTML = minute_text + ' ' + (element.seconds%60) + ' ' + second_text + ' remaining'; 

     element.seconds--; 

    }, 1000); 
}​ 

(я бы разместить в качестве комментария, если бы не всего код ...)

+0

Да, но это был код, не оптимизированный из другого вопроса. Моя проблема заключалась в закрытии. Спасибо – mplungjan

1

Вы декремент неправильных переменного внутри вашего интервал обратного вызова. Вместо того, чтобы делать seconds-- и minutes--, вам необходимо обратиться к членам element.

intervals[element.id] = setInterval(function() { 
    var el = document.getElementById(element.id); 

    if(element.seconds == 0) { 
     if(element.minutes == 0) { 
      el.innerHTML = "countdown's over!";      
      clearInterval(intervals[element.id]); 
      return; 
     } else { 
      element.minutes--; 
      element.seconds = 60; 
     } 
    } 
    if(element.minutes > 0) { 
     var minute_text = element.minutes + (element.minutes > 1 ? ' minutes' : ' minute'); 
    } else { 
     var minute_text = ''; 
    } 
    var second_text = element.seconds > 1 ? 'seconds' : 'second'; 
    el.innerHTML = minute_text + ' ' + element.seconds + ' ' + second_text + ' remaining'; 
    element.seconds--; 
}, 1000); 
+0

Это очень близко к тому, что у меня было до того, как я сделал беспорядок закрывающей версии. Я думаю, что лучше всего Джон, плюс его дополнения. Яснее! – mplungjan

1

Все ответы вы получили до сих пор не в состоянии справиться один простой факт: setInterval делает не случается надежно в заданное вами время. Он может быть отложен или даже пропустил, если браузер занят чем-то другим. Это означает, что вы не можете полагаться на свою функцию обновления, чтобы уменьшить количество оставшихся секунд, вы быстро начнете дрейфовать от реальности. Может быть, все в порядке, может быть, нет. В этом случае нет : просто вычислите, когда происходит тайм-аут (например, минута и пять секунд с вашего счетчика «обувь»), а затем при каждом обновлении подсчитайте, сколько времени у вас осталось. Таким образом, если отброшен интервал или что-то еще, вы не будете дрейфовать; вы откладываете часы компьютера.

Вот процедурный версия:

// Note that to prevent globals, everything is enclosed in 
// a function. In this case, we're using window.onload, but 
// you don't have to wait that long (window.onload happens 
// *very* late, after all images are loaded). 
window.onload = function() { 

    // Our counters 
    var counters = { 
    "shoes":{ 
     "id": "shoe1", 
     "minutes": 1, 
     "seconds":5 
    }, 
    "trousers":{ 
     "id": "trouser1", 
     "minutes": 10, 
     "seconds":0 
    } 
    }; 

    // Start them 
    var name; 
    for (name in counters) { 
    start(counters[name]); 
    } 

    // A function for starting a counter 
    function start(counter) { 
    // Find the time (in ms since The Epoch) at which 
    // this item expires 
    counter.timeout = new Date().getTime() + 
         (((counter.minutes * 60) + counter.seconds) * 1000); 

    // Get this counter's target element 
    counter.element = document.getElementById(counter.id); 
    if (counter.element) { 
     // Do the first update 
     tick(counter); 

     // Schedule the remaining ones to happen *roughly* 
     // every quarter second. (Once a second will look 
     // rough). 
     counter.timer = setInterval(function() { 
     tick(counter); 
     }, 250); 
    } 
    } 

    // Function to stop a counter 
    function stop(counter) { 
    if (counter.timer) { 
     clearInterval(counter.timer); 
     delete counter.timer; 
    } 
    delete counter.element; 
    } 

    // The function called on each "tick" 
    function tick(counter) { 
    var remaining, str; 

    // How many seconds left? 
    remaining = Math.floor(
     (counter.timeout - new Date().getTime())/1000 
    ); 

    // Same as last time? 
    if (remaining != counter.lastRemaining) { 
     // No, do an update 
     counter.lastRemaining = remaining; 
     if (remaining <= 0) { 
     // Done! Stop the counter. 
     str = "done"; 
     alert("Stopped " + counter.id); 
     stop(counter); 
     } 
     else { 
     // More than a minute left? 
     if (remaining >= 120) { 
      // Yup, output a number 
      str = Math.floor(remaining/60) + " minutes"; 
     } 
     else if (remaining >= 60) { 
      // Just one minute left 
      str = "one minute"; 
     } 
     else { 
      // Down to seconds! 
      str = ""; 
     } 

      // Truncate the minutes, just leave seconds (0..59) 
     remaining %= 60; 

     // Any seconds? 
     if (remaining > 0) { 
      // Yes, if there were minutes add an "and" 
      if (str.length > 0) { 
      str += " and "; 
      } 

      // If only one second left, use a word; else, 
      // a number 
      if (remaining === 1) { 
      str += "one second"; 
      } 
      else { 
      str += Math.floor(remaining) + " seconds"; 
       } 
     } 

     // Finish up 
     str += " left"; 
     } 

     // Write to the element 
     counter.element.innerHTML = str; 
    } 
    } 

};​ 

Live example

Вот версия ООП (с использованием шаблона модуля так Counter может иметь named functions и частный один [tick]):

// A Counter constructor function 
var Counter = (function() { 
    var p; 

    // The actual constructor (our return value) 
    function Counter(id, minutes, seconds) { 
    this.id = id; 
    this.minutes = minutes || 0; 
    this.seconds = seconds || 0; 
    } 

     // Shortcut to the prototype 
    p = Counter.prototype; 

    // Start a counter 
    p.start = Counter_start; 
    function Counter_start() { 
    var me = this; 

    // Find the time (in ms since The Epoch) at which 
    // this item expires 
    this.timeout = new Date().getTime() + 
         (((this.minutes * 60) + this.seconds) * 1000); 

    // Get this counter's target element 
    this.element = document.getElementById(this.id); 
    if (this.element) { 
     // Do the first update 
     tick(this); 

     // Schedule the remaining ones to happen *roughly* 
     // every quarter second. (Once a second will look 
     // rough). 
     this.timer = setInterval(function() { 
     tick(me); 
     }, 250); 
    } 
    } 

    // Stop a counter 
    p.stop = Counter_stop; 
    function Counter_stop() { 
    if (this.timer) { 
     clearInterval(this.timer); 
     delete this.timer; 
    } 
    delete this.element; 
    } 

    // The function we use to update a counter; not exported 
    // on the Counter prototype because we only need one for 
    // all counters. 
    function tick(counter) { 
    var remaining, str; 

    // How many seconds left? 
    remaining = Math.floor(
     (counter.timeout - new Date().getTime())/1000 
    ); 

    // Same as last time? 
    if (remaining != counter.lastRemaining) { 
     // No, do an update 
     counter.lastRemaining = remaining; 
     if (remaining <= 0) { 
     // Done! Stop the counter. 
     str = "done"; 
     alert("Stopped " + counter.id); 
     stop(counter); 
     } 
     else { 
     // More than a minute left? 
     if (remaining >= 120) { 
      // Yup, output a number 
      str = Math.floor(remaining/60) + " minutes"; 
     } 
     else if (remaining >= 60) { 
      // Just one minute left 
      str = "one minute"; 
     } 
     else { 
      // Down to seconds! 
      str = ""; 
     } 

     // Truncate the minutes, just leave seconds (0..59) 
     remaining %= 60; 

     // Any seconds? 
     if (remaining > 0) { 
      // Yes, if there were minutes add an "and" 
      if (str.length > 0) { 
      str += " and "; 
      } 

      // If only one second left, use a word; else, 
      // a number 
      if (remaining === 1) { 
      str += "one second"; 
      } 
      else { 
      str += Math.floor(remaining) + " seconds"; 
      } 
     } 

     // Finish up 
     str += " left"; 
     } 

     // Write to the element 
     counter.element.innerHTML = str; 
    } 
    } 

    // Return the constructor function reference. This 
    // gets assigned to the external var, which is how 
    // everyone calls it. 
    return Counter; 
})(); 

// Note that to prevent globals, everything is enclosed in 
// a function. In this case, we're using window.onload, but 
// you don't have to wait that long (window.onload happens 
// *very* late, after all images are loaded). 
window.onload = function() { 

    // Our counters 
    var counters = { 
    "shoes": new Counter("shoe1", 1, 5), 
    "trousers": new Counter("trouser1", 10, 0) 
    }; 

    // Start them 
    var name; 
    for (name in counters) { 
    counters[name].start(); 
    } 

};​ 

Live example

More about closures here.

+0

На самом деле я знал это и сделал то, что вы предлагаете, не полагаясь на промежуток в прошлом. Спасибо, что напомнили мне и о хорошем примере OO. Не знаете, почему вам нужно удалить таймер и элемент - сбор мусора? – mplungjan

+0

@mplungjan: Важно, чтобы объект JavaScript не содержал ссылку на элемент DOM без необходимости (особенно на IE), и поскольку мне больше не нужно ни одного из этих свойств, «удаление» казалось естественным делать. Я мог бы просто назначить 'undefined' им или чему-то, но почему бы не полностью очистить. :-) –

+0

Вот версия jQuery: http://jsbin.com/oyoba3/edit фактическое обсуждение находится здесь: http://stackoverflow.com/questions/4584397/javascript-countdown-clock/4584501 – mplungjan

Смежные вопросы