Сегодня я решил подробно рассказать о том как можно сделать таймер обратного отсчета в 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>