JavaScript: Создаем таймер обратного отсчета своими руками

Сегодня я решил подробно рассказать о том как можно сделать таймер обратного отсчета в JavaScript без использования сторонних библиотек и JQuery.

Я думаю многие из Вас хотят в своих проектах использовать свои наработки, но часто либо на это не хватает времени, либо навыков. Поэтому сегодня я разберу задачу создания таймера обратного отсчета в двух вариантах: таймер до определенной даты и таймер на заданное количество часов, минут и секунд. Некоторые наверное поняли что на самом деле основная часть работы уйдет на решение первой задачи, так как вторую задачу можно легко привести к первой (хочется отметить что умение грамотно сводить свои задачи к уже решенным значительно облегчает процесс разработки). И так приступим…

Для начала определимся с тем как мы будет реализовывать наш таймер: функции, объект или имитация класса. Функции я отбрасываю сразу, это не очень удобно, а вот объект и «класс» звучит вполне не плохо. Однако есть еще один немаловажный момент: хотим ли мы использовать несколько таймеров на одной странице? Если да, то реализация в виде объекта немного усложнит нашу задачу, так как объект по сути — статический класс, и нам придется реализовывать работу с массивом таймеров. Классы же никак не пересекаются и можно создавать неограниченое число таймеров, поэтому остановимся на этом варианте. Заодно определимся с тем, что таймер должен создаваться в указанном элементе без ручного создания дополнительной разметки. И так поехали…

Создадим заготовку для создания js «класса»:

var fsTimer = function(elementId) { //конструктор
  var element = document.getElementById(elementId);
  if(element == null) {
    throw "Элемент с id='" + elementId + "' не найден";
  } 
  this.Element = element; //Сохраним элемент внутри объекта
}

Если присмотреться, то нам явно не хватает настроек. Расширим наш конструктор вторым аргументом, отвечающим за настройки нашего таймера. Настройки будем реализовывать как объект, который будем сравнивать с настройками по умолчанию. Т.е. например у нас есть настройки «a», «b», «c» в конструктор мы передаем только «a» и «с» => в нашем таймере настройки «a» и «c» будут равны указанным, а «b» возьмется по умолчанию. Такой подход используется в большинстве скриптов, так как он очень удобен. Так же нам придется реализовать такой механизм сравнения настроек, так как в штатных средсвах JS его нет. Так же заодно напишем код разметки нашего будующего таймера.

var fsTimer = function(elementId, options) { //конструктор
  var element = document.getElementById(elementId);
  if(element == null) {
    throw "Элемент с id='" + elementId + "' не найден";
  } 
  this.Element = element; //Сохраним элемент внутри объекта
  options = options || {}; //Если настройки не передали, то это пустой объект      
  this.Options = {
    showDays: true, //показываем ли сколько осталось дней
    endDate: new Date((new Date()).getTime() + (24 * 60 * 60 * 1000)), //по умолчанию таймер на сутки,
    timer: null, //если будет определен, то это второй вариант нашей задачи
    onComplete: function() {} //функция при завершении обратного отсчета
  }
        
  for (var attrName in options) { //перенесем поля из options в Options таймера
    this.Options[attrName] = options[attrName]; 
  }
        
  element.innerHTML = 
    (this.Options.showDays ? "<div id='" + elementId + "-fsTimerNumberDays' class='fsTimerNumber fsTimerNumberDays' style='float:left;'>0</div>" : "") +
    "<div id='" + elementId + "-fsTimerNumberHours' class='fsTimerNumber fsTimerNumberHours' style='float:left;'>0</div>" +
    "<div id='" + elementId + "-fsTimerNumberMinutes' class='fsTimerNumber fsTimerNumberMinutes' style='float:left;'>0</div>" +
    "<div id='" + elementId + "-fsTimerNumberSeconds' class='fsTimerNumber fsTimerNumberSeconds' style='float:left;'>0</div>" +
    "<div style='clear:both;'></div>";
}

Как видно я сразу указал классы у div элементов, так как это облегчит создания стилей для таймера как Вам так и тем, кто будет использовать Ваш таймер. Теперь у нас имеется заготовка. Переходим непосредсвенно к решению первой задачи (таймер до определенной даты). Напишем функцию, которая найдет там разницу дней, часов, минут и секунд от указанной даты, до текущей даты. И опять таки отсупление о котором не стоит забывать. JavaScript использует локальную дату компьютера, Вам же часто потребуется вести отсчет времени опираясь на дату сервера. Поэтому добавим еще один параметр в наши настройки — текущее время, и приступим к вычислениям.

var fsTimer = function(elementId, options) { //конструктор
  var element = document.getElementById(elementId);
  if(element == null) {
    throw "Элемент с id='" + elementId + "' не найден";
  } 

  var now = new Date(); //время компьютера
  this.Element = element; //Сохраним элемент внутри объекта
  options = options || {}; //Если настройки не передали, то это пустой объект      
  
  this.Options = {
    showDays: true, //показываем ли сколько осталось дней
    now: now,
    endDate: new Date(now.getTime() + (24 * 60 * 60 * 1000)), //по умолчанию таймер на сутки,
    timer: null, //если будет определен, то это второй вариант нашей задачи
    onComplete: function() {} //функция при завершении обратного отсчета
  }
        
  for (var attrName in options) { //перенесем поля из options в Options таймера
    this.Options[attrName] = options[attrName]; 
  }
   
  if(this.Options.endDate <= this.Options.now) { //добавим проверку
    throw "Дата окончания таймера не может быть меньше текущей даты";
  }
  
  var timerData = this.CalculateTimer(this.Options.now, this.Options.endDate);

  element.innerHTML = 
    (this.Options.showDays ? "<div id='" + elementId + "-fsTimerNumberDays' class='fsTimerNumber fsTimerNumberDays' style='float:left;'>" + timerData.days + "</div>" : "") +
    "<div id='" + elementId + "-fsTimerNumberHours' class='fsTimerNumber fsTimerNumberHours' style='float:left;'>" + timerData.hours + "</div>" +
    "<div id='" + elementId + "-fsTimerNumberMinutes' class='fsTimerNumber fsTimerNumberMinutes' style='float:left;'>" + timerData.minutes + "</div>" +
    "<div id='" + elementId + "-fsTimerNumberSeconds' class='fsTimerNumber fsTimerNumberSeconds' style='float:left;'>" + timerData.seconds + "</div>" +
    "<div style='clear:both;'></div>";
}
fsTimer.prototype.CalculateTimer = function(dateFrom, dateTill) { //Высчитываем разницу между датами
  var d = 0, h = 0, m = 0, s = 0;
  var different = dateTill.getTime() - dateFrom.getTime();
  d = Math.floor(different / (24 * 60 * 60 * 1000)); //получаем число дней ((((1000мс * 60)сек * 60)мин * 24)ч = 1день)
  //Math.floor - округляет до целого в меньшую сторону, это необходимо так
  //как в js операция деления не целочислена
  different = different % (24 * 60 * 60 * 1000);
  h = Math.floor(different / (60 * 60 * 1000)); //получаем число часов
  different = different % (60 * 60 * 1000);
  m = Math.floor(different / (60 * 1000)); //получаем число минут
  different = different % (60 * 1000);
  s = Math.floor(different / 1000); //получаем число секунд
  return { days: d, hours: h, minutes: m, seconds: s};
}

Для тех кто не понимает что значит запись fsTimer.prototype.CalculateTimer советую посмотреть на эту запись: JavaScript: Классы или скриптовый ООП.
Теперь пора проверить наше творение. Создадим примитивный html документ и попробуем создать таймер.

<html>
<head>
<script src="fsTimer.js"></script>
</head>
<body>
<div id="timer1"></div>
<script>
var timer = new fsTimer('timer1');
</script>
</body>
</html>

Если все верно, то у Вас должно отобразиться 1000 на вашей странице. Немного непонятно что к чему, поэтому добавим стиль, что бы хоть как то разделить наши цифры.

<style>
.fsTimerNumber { padding: 10px; border: 1px solid #000; text-align: center;}
</style>

Теперь получше… Переходим к основной части нашего таймера — непосредственно логика отсчета таймер назад. На самом деле ничего сложного в этом нет, поэтому привиду сразу код функции с небольшими комментариями.

      fsTimer.prototype.Tick = function(instance) { //Уменьшение таймера на 1 секунду
        var d = document.getElementById(instance.Element.getAttribute('id') + "-fsTimerNumberDays"),
            h = document.getElementById(instance.Element.getAttribute('id') + "-fsTimerNumberHours"),
            m = document.getElementById(instance.Element.getAttribute('id') + "-fsTimerNumberMinutes"),
            s = document.getElementById(instance.Element.getAttribute('id') + "-fsTimerNumberSeconds");    
        if(h == null || m == null || s == null) {
          throw "Не найдены элементы таймера";
        }
        var intD = d == null ? 0 : parseInt(d.innerHTML), intH = parseInt(h.innerHTML),
            intM = parseInt(m.innerHTML), intS = parseInt(s.innerHTML);
        //Уменьшаем секунду на 1, если все по нулям, то конец таймера, иначе если 
        //число отрицательно, значит нужно уменьшить минуту и т.д.
        --intS;
        if(intS < 0) { 
          intS = 59;
          --intM;
        }
        if(intM < 0) { 
          intM = 59;
          --intH;
        }
        if(intH < 0) {
          if(intD > 0) {
            intH = 23;
            --intD;
          } else { //Такого быть не может, так как таймер должен был завершиться
            throw "Ошибка вычислений";
          }
        }
        //Для красоты выровним длинну цифр до двух
        if(intD.toString().length < 2) intD = '0' + intD;
        if(intH.toString().length < 2) intH = '0' + intH;
        if(intM.toString().length < 2) intM = '0' + intM;
        if(intS.toString().length < 2) intS = '0' + intS;
        if(d != null) { 
          d.innerHTML = intD;
        }       
        h.innerHTML = intH;
        m.innerHTML = intM;
        s.innerHTML = intS;
        if(intD == 0 && intH == 0 && intM == 0 && intS == 0) { //Если отсчет закончен
          if(typeof(this.Options.onComplete) == 'function') {
            instance.Options.onComplete();
          }
          clearInterval(instance.Interval);
          return;
        }
      }

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

var instance = this;  
this.Interval = setInterval(function() { instance.Tick(instance); }, 980); 

Теперь если посмотреть на нашу странцу, то таймер должен работать. Осталось решить нашу вторую задачу. Подход к решению такой: если таймеру указан параметр timer с количеством часов, минут и секунд, то вычисляем дату которая будет через заданный интервал, ставим ее как дату окончания таймера и запускам таймер по алгоритму задачи один. Я добалю данный механимз в конструктор. И сразу приведу весь заключительный код нашего таймера.

var fsTimer = function(elementId, options) { //конструктор
        var element = document.getElementById(elementId);
        if(element == null) {
          throw "Элемент с id='" + elementId + "' не найден";
        } 
        
        var now = new Date(); //время компьютера
        this.Element = element; //Сохраним элемент внутри объекта
        options = options || {}; //Если настройки не передали, то это пустой объект
        
        this.Options = {
          showDays: true, //показываем ли сколько осталось дней
          now: now,
          endDate: new Date(now.getTime() + (24 * 60 * 60 * 1000)), //по умолчанию таймер на сутки,
          timer: null, //если будет определен, то это второй вариант нашей задачи
          onComplete: function() {} //функция при завершении обратного отсчета
        }
             
        for (var attrName in options) { //перенесем поля из options в Options таймера
          this.Options[attrName] = options[attrName]; 
        }
        
        if(this.Options.timer != null) {
          this.Options.timer.hours = this.Options.timer.hours || 0;
          this.Options.timer.minutes = this.Options.timer.minutes || 0;
          this.Options.timer.seconds = this.Options.timer.seconds || 1;  
          this.Options.endDate = new Date(this.Options.now.getTime() + 
            (this.Options.timer.hours * 60 * 60 * 1000) +
            (this.Options.timer.minutes * 60 * 1000) + 
            (this.Options.timer.seconds * 1000));  
        } 
        
        if(this.Options.endDate <= this.Options.now) {
          throw "Дата окончания таймера не может быть меньше текущей даты";
        }
        
        var timerData = this.CalculateTimer(this.Options.now, this.Options.endDate);
        
        element.innerHTML = 
          (this.Options.showDays ? "<div id='" + elementId + "-fsTimerNumberDays' class='fsTimerNumber fsTimerNumberDays' style='float:left;'>" + timerData.days + "</div>" : "") +
          "<div id='" + elementId + "-fsTimerNumberHours' class='fsTimerNumber fsTimerNumberHours' style='float:left;'>" + timerData.hours + "</div>" +
          "<div id='" + elementId + "-fsTimerNumberMinutes' class='fsTimerNumber fsTimerNumberMinutes' style='float:left;'>" + timerData.minutes + "</div>" +
          "<div id='" + elementId + "-fsTimerNumberSeconds' class='fsTimerNumber fsTimerNumberSeconds' style='float:left;'>" + timerData.seconds + "</div>" +
          "<div style='clear:both;'></div>";
        
        var instance = this;  
        this.Interval = setInterval(function() { instance.Tick(instance); }, 980);  
      }
      
      fsTimer.prototype.CalculateTimer = function(dateFrom, dateTill) { //Высчитываем разницу между датами
        var d = 0, h = 0, m = 0, s = 0;
        var different = dateTill.getTime() - dateFrom.getTime();
        d = Math.floor(different / (24 * 60 * 60 * 1000)); //получаем число дней ((((1000мс * 60)сек * 60)мин * 24)ч = 1день)
        //Math.floor - округляет до целого в меньшую сторону, это необходимо так
        //как в js операция деления не целочислена
        different = different % (24 * 60 * 60 * 1000);
        h = Math.floor(different / (60 * 60 * 1000)); //получаем число часов
        different = different % (60 * 60 * 1000);
        m = Math.floor(different / (60 * 1000)); //получаем число минут
        different = different % (60 * 1000);
        s = Math.floor(different / 1000); //получаем число секунд
        return { days: d, hours: h, minutes: m, seconds: s};
      }
      
      fsTimer.prototype.Tick = function(instance) { //Уменьшение таймера на 1 секунду
        var d = document.getElementById(instance.Element.getAttribute('id') + "-fsTimerNumberDays"),
            h = document.getElementById(instance.Element.getAttribute('id') + "-fsTimerNumberHours"),
            m = document.getElementById(instance.Element.getAttribute('id') + "-fsTimerNumberMinutes"),
            s = document.getElementById(instance.Element.getAttribute('id') + "-fsTimerNumberSeconds");    
        if(h == null || m == null || s == null) {
          throw "Не найдены элементы таймера";
        }
        var intD = d == null ? 0 : parseInt(d.innerHTML), intH = parseInt(h.innerHTML),
            intM = parseInt(m.innerHTML), intS = parseInt(s.innerHTML);
        //Уменьшаем секунду на 1, если все по нулям, то конец таймера, иначе если 
        //число отрицательно, значит нужно уменьшить минуту и т.д.
        --intS;
        if(intS < 0) { 
          intS = 59;
          --intM;
        }
        if(intM < 0) { 
          intM = 59;
          --intH;
        }
        if(intH < 0) {
          if(intD > 0) {
            intH = 23;
            --intD;
          } else { //Такого быть не может, так как таймер должен был завершиться
            throw "Ошибка вычислений";
          }
        }
        //Для красоты выровним длинну цифр до двух
        if(intD.toString().length < 2) intD = '0' + intD;
        if(intH.toString().length < 2) intH = '0' + intH;
        if(intM.toString().length < 2) intM = '0' + intM;
        if(intS.toString().length < 2) intS = '0' + intS;
        if(d != null) {
          d.innerHTML = intD;
        }
        h.innerHTML = intH;
        m.innerHTML = intM;
        s.innerHTML = intS;
        
        if(intD == 0 && intH == 0 && intM == 0 && intS == 0) {
          if(typeof(this.Options.onComplete) == 'function') {
            instance.Options.onComplete();
          }
          clearInterval(instance.Interval);
          return;
        }
      }

И небольшой демонстрирующий пример:

<html>
<head>
<script src="fsTimer.js"></script>
<style>
.fsTimerNumber { padding: 10px; border: 1px solid #000; text-align: center;}
</style>
</head>
<body>
<div id="timer1"></div>
<div id="timer2"></div>
<script>
var timer1 = new fsTimer('timer1', { 
   timer: { seconds: 20 }, 
   showDays: false,
   onComplete: function() { alert('Таймер 1 от блога доброго программиста!'); } 
});
var timer2 = new fsTimer('timer2', { 
   timer: { minutes: 1, seconds: 2 }, 
   onComplete: function() { alert('Таймер 2 от блога доброго программиста!'); } 
});
</script>
</body>
</html>
Запись опубликована в рубрике JavaScript с метками , , , , , , , , . Добавьте в закладки постоянную ссылку.