Сломанное закрытие - пожалуйста, помогите мне исправить это - PullRequest
0 голосов
/ 03 января 2011

в связанном вопросе Я разместил этот код.Это почти работает, но счетчик не работает.

Можем ли мы это исправить?(без 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 />

Ответы [ 5 ]

4 голосов
/ 03 января 2011

Вам просто нужно извлечь объявления переменных 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, вы хотите получить начальные значения для каждого обратного отсчета, а затем медленно уменьшить их (закрытие гарантирует, что значения будут оставаться доступными, пока их требует анонимная функция). То, что вы делали до этого, сбрасывало значения обратного отсчета в начале каждого тика, поэтому у обратного отсчета никогда не было шанса ... ну, отсчет.

Обновление:

Если вам нужно обновить значения внутри 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;
4 голосов
/ 03 января 2011

Пожалуйста, отметьте это

<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>

Рабочий раствор здесь .

РЕДАКТИРОВАТЬ: была небольшая проблема со сценарием. Я исправил это.

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

1 голос
/ 03 января 2011

Все ответы, которые вы получили до сих пор, не могут обработать один простой факт: 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;
    }
  }

};​

Живой пример

Вот версия ООП (с использованием модулятаким образом, Counter может иметь с именами функций и частными [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();
  }

};​

Живой пример

Подробнее о замыканиях здесь .

1 голос
/ 03 января 2011

Вы уменьшаете неправильную переменную внутри вашего интервала обратного вызова.Вместо 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 голосов
/ 03 января 2011

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

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);
}​

(Iопубликовал бы как комментарий, если бы не весь код ...)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...