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