Меню

Първи стъпки в правене на сайтове

Влез Излез

JavaScript Хронометър – част 2

За момента сме направили една семпла страница с основен HTML markup, който да ни послужи за създаването на простичък JavaScript хронометър. Приложили сме нужните стилове, заредили сме jQuery и сме задали функция, която да се изпълни при DOMContentLoaded. Остава ни единствено да добавим и програмтана логика на нашия хронометър, за да може той да ни свърши някаква работа когато ни се наложи.

Нека започнем с една незначителна редакция на HTML-а и CSS-а, която изглежда съм пропуснал предния път. Нека стилът на #stopwatch a, който гласи display: inline-block; го преместим на "#stopwatch a, #stopwatch span", така че частта със стиловете ни стане:

#stopwatch { margin: 50px 0; text-align: center; font-size: 24px; }

#stopwatch a,
#stopwatch span { display: inline-block; padding: 0 15px; line-height: 1.6; }

#stopwatch a { background: #404040; color: #FFF; text-decoration: none; }
#stopwatch a:hover,
#stopwatch a:focus { background: #666; }
#stopwatch span { background: #000; color: #FFF; }

Освен това нека променим текста на хронометъра от 00:00:00 на 00:00:00,000 – просто добавяме и милисекунди, за да наблюдаваме по-детайлно промените във времето на хронометъра.

Нека най-сетне се заловим за работа.

Аз самият предпочитач първо да си създам няколко променливи, които ще използвам в кода си, след което вече променям стойността им:

jQuery(function ($) {
	var trigger = $('#stopwatch a');
	var label = $('#stopwatch span');
});

В тези две променливи ще запишем референция към елементите на страницата, които ще използваме. Аз лично предпочитам да ги запиша в отделни променливи поради две причини:

  1. по-нататъшното използване е по-лесно, отколкото отново да пишем селектора
  2. селектираме DOM елемент само веднъж, защото това действие по принцип е бавно, така че е добре да огранимич селектирането на отделни възли от markup-а си

След това нека да зададем click handler на нашия линк:

jQuery(function ($) {
	var trigger = $('#stopwatch a');
	var label = $('#stopwatch span');
	
	trigger.click(function (e) {
		e.preventDefault();
	});
});

Извикваме e.preventDefault() за да ограничим действието по подразбиране при кликването на линк, което ще се опита да ни прати на друга страница (в случая href атрибутат е просто „#“, което всъщност няма да ни прати никъде, но все пак би ни върнало най-горе в страницата, което понякога може да е нежелан ефект).

Сега трябва да направим проверка в кода си, дали хронометъра вече не върви и в зависимост от това трябва да го пуснем или да го спрем. Самото пускане нека асоциираме с нова променлива, чиято стойност всъщност ще бъде и същинската проверка. В тази променлива ще запазим едно повтарящо се действие, което ще обновява показанието на хронометъра. Ако тази прометлива няма стойност – стартираме, в противен случай – спираме.

jQuery(function ($) {
	var timer = null;
	var startTime = 0;
	
	var trigger = $('#stopwatch a');
	var label = $('#stopwatch span');
	
	trigger.click(function (e) {
		e.preventDefault();
		
		if (!timer) {
			startTime = new Date();
		} else {
			
		}
	});
});

Създаваме и променливата startTime, която приравняваме първоначално на 0, а при стартиране на хронометъра задаваме стойността й да бъде моментът на стартирането. Сега вече следва да зададем и това повтарящо се действие, за което споменахме преди малко. Все още не сме говорили за подобни неща, но в JavaScript имаме възможност да извикаме дадена функция след определено време, използвайки setTimeout() или да зададем периодично изпълнение на функция, през даден интервал чрез setInterval(). Самият синтаксис на функциите е еднакъв с разлика в това, че setTimeout() изпълнява функцията само веднъж, а setInterval() я изпълнява безкрайно (или поне докато не я отменим чрез clearInterval()).

jQuery(function ($) {
	var FRAME_PERIOD = 1000/60;
	
	var timer = null;
	var startTime = 0;
	
	var trigger = $('#stopwatch a');
	var label = $('#stopwatch span');
	
	trigger.click(function (e) {
		e.preventDefault();
		
		if (!timer) {
			startTime = new Date();
			
			timer = setInterval(function () {
				var currentTime = new Date(new Date() - startTime);
				
				label.text(formatTime(currentTime));
			}, FRAME_PERIOD);
			
			trigger.text('Stop');
		} else {
			clearInterval(timer);
			timer = false;
			
			trigger.text('Start');
		}
	});
});

Ето и коментарите към последния код:

В началото виждате добавената променлива FRAME_PERIOD. Изписана е с главни букви, тъй като идеята й е на константа, но както вече сме говорили – в JavaScript не можем да задаваме константи, затова използваме обикновена променлива. Стойността й задаваме на 1000/60. От къде идва това число. Това е периодът на обновяване на картината на мониторите. Имаме 1000 милисекунди за секунда разделени на 60 Hz (обикновено поне е толкова) честота на опресняване на картината, отново за секунда.

В click handler-а на линка добавяме и код, който присвоява на променливата timer периодично повтаряща се функция, зададена чрез setInterval. Последната функция приема първи аргумент функция, която да изпълни, а като втори – период, който да минава между изпълнението на функцията. Този период задаваме на периодът на обновяване на картината на екрана. Това е така, защото тази функция всъщност ще изписва колко време е минало, а за да изпишем нещо, трябва да имаме обновяване на картината – иначе просто няма да можем да го прочетем.

В тялото на периодичната функция създаваме локална променлива, чиято стойност е дата. Тази дата обаче не е текущият момент, а е момент, състоял се толкова милисекунди след началоно та компютърната епоха (01.01.1970 00:00:00), колкото милисекунди е разликата между настоящият момент и моментът на започване на хронометъра. Казано с други думи – startTime е брой милисекунди в момента на натискане на бутона „Start“. Ние обаче изваждаме този брои милисекунди от текущия момент, така че получаваме разликата от натискането на „Start“ до сега. Тази разлика третираме като нов момент, подавайки я като агрумент към new Date(). След това на label задаваме текст базиран на това време, но прекаран през функция, която сега ще опишем. Задачата на тази функция е да представи времето във хормат „00:00:00,000“.

function formatTime(time) {
	return leadingZero(time.getUTCHours()) + ':' + leadingZero(time.getMinutes()) + ':' + leadingZero(time.getSeconds()) + ',' + leadingZero(time.getMilliseconds(), 3);
}

В тази функция използваме друга – leadingZero(), която ще напишем след малко (нейната задача ще е да представи едноцифрени числа с нули отпреде, така че вместо 0:0:1,123 да имаме 00:00:01,123). Това, което правим във formatTime() е да конкатенираме (свържем стрингове) от елементине на времето, които взимаме чрез няколко вградени функции за Date обектите – getUTCHours(), getMinutes(), getSeconds() и getMilliseconds(). Забележете, че използваме getUTCHours() вместо обикновеното getHours(), защото getHours() връща часове според нашата часова зона, и изчислявайки разлика между моменти, ние получаваме разликата като период от времето по Гринуич. Така че в моя случай getHours() би върнало 2, тъй като съм в GMT+2 часова зона (България).

А ето така изглежда и функцията leadingZero():

function leadingZero(num, length) {
	if (typeof(length) == 'undefined') length = 2;
	
	while (num.toString().length < length) {
		num = '0' + num;
	}
	
	return num;
}

Първо забележете използването на втори аргумент. Извикване на функцията използвайки го имаме при форматирането на милисекундите, тъй като те не трябва да са само с 2 цифри, а с три. Именно броят на цифрите в числото опоменаваме чрез този втори аргумент.

Тези, които са се занимавали с програмиране най-вероятно знаят, че при дефиниране на функции, можем да зададем някои от аргументите като незадължителни именно когато им зададем стойност по подразбиране. В повечето езици това би изглеждало като:

function leadingZero(num, length=2) {
...
}

В JavaScript това обаче не би имало никакъв ефект. Останалите езици биха дали грешка, ако функцията е дефинирана с няколко агрумента, а се извика с различен брой такива. В JavaScript обаче ние просто бихме могли да "прихванем" стойността на толкова от подадените аргументи, колкото сме задали при дефинирането на функцията. В случай, че са дефинирани повече, отколкото подадени при извикване, излишните аргументи ще бъдат просто недефинирани променливи. Точно затова правим проверката if (typeof(length) == 'undefined'), което проверява дали променливана е недефинирана. Ако е, тогава трябва да използваме нейната стойност по подразбиране, затова й присвояваме стойност 2.

След това с цикъл толкова пъти, колкото е разликата между цифрите на числото и желаната дължина на числото, която се определя от втория аргумент на функцията, добавяме нули преди числовата стойност. Още след първото добавяне на 0, променливата вече не е от числов тип, а се третира като низов литерал (стринг). Защо и какво определя това ще обясня в друг урок, за момента просто е добре да го знаете на готово.

Ето го и пълният JavaScript:

jQuery(function ($) {
	var FRAME_PERIOD = 1000/60;
	var timer = null;
	var startTime = 0;
	
	var trigger = $('#stopwatch a');
	var label = $('#stopwatch span');
	
	function leadingZero(num, length) {
		if (typeof(length) == 'undefined') length = 2;
		
		while (num.toString().length < length) {
			num = '0' + num;
		}
		
		return num;
	}
	
	function formatTime(time) {
		return leadingZero(time.getUTCHours()) + ':' + leadingZero(time.getMinutes()) + ':' + leadingZero(time.getSeconds()) + ',' + leadingZero(time.getMilliseconds(), 3);
	}
	
	trigger.click(function (e) {
		e.preventDefault();
		
		if (!timer) {
			startTime = new Date();
			
			timer = setInterval(function () {
				var currentTime = new Date(new Date() - startTime);
				
				label.text(formatTime(currentTime));
			}, FRAME_PERIOD);
			
			trigger.text('Stop');
		} else {
			clearInterval(timer);
			timer = false;
			
			trigger.text('Start');
		}
	});
});

Demo на страницата пък можете да видите на http://magadanski.com/demo/stopwatch/

Вашият коментар

Вашият имейл адрес няма да бъде публикуван. Задължителните полета са отбелязани с *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax