Аналоговые часы на HTML5 c логикой на JavaScript

от автора

Для кого и зачем эта статья?

Когда изучаешь новую технологию или язык программирования, основные понятия всегда носят относительно рутинный характер и поэтому, на мой взгляд, быстро отбивают желание обучаться у начинающих. Цель данной статьи — это заинтересовать и увлечь читателя изучением программирования на примере разработки элементарной графики в динамическом режиме. Статья подойдет для начинающих разработчиков, которые ознакомились с основами HTML5 и JavaScript, и которым наскучило видеть статический текст на страничке при выводе в консоль браузера массивов, объектов, результатов арифметических операций и т.д. Далее мы реализуем простейшую, но полезную для понимания языка анимацию.

Что мы будем делать?

Рассмотрим процесс создание простейших аналоговых часов средствами HTML5 и JavaScript. Рисовать часы будем графическими примитивами, не используя средств CSS. Мы вспомним немного геометрии для отображения нашей графики, вспомним немного математики для реализации логики отображения наших анимированных часов. И в целом постараемся уменьшить энтропию в познаниях языка JavaScript. Для разработки нам понадобится текстовый редактор вроде Notepad++ или Sublime Text 3.

Реализация цифровых часов

Создадим три файла в текстовом редакторе. (Все три файла должны лежать в одной папке).

index.html — основная страничка
clockscript.js — скрипт с логикой работы
style.css — файл стилей

Для начала выведем текущее время в обычный div-блок в .html файл. Даже в такой маленькой задаче есть свой подводный камень. Если просто закинуть функцию отображения часов в событие onload у тега body, то текущее время отобразится в строке, но так и останется статическим. И div-блок, в который мы отправили строку с текущим временем, не будет самостоятельно обновляться.

Добиться самостоятельного обновления элемента страницы можно оборачиванием функции отображения времени в анонимный метод, который присваивается свойству onload корневого объекта Window.

Один из вариантов реализации может быть следующим. Файл index.html:

<!DOCTYPE html> <html>   <head> 	<title>Часы</title> 	<meta http-equiv = "Content-Type" content = "text/html; charset = utf-8" > 	<link rel = "stylesheet" type = "text/css" href = "style.css"> 	<script src = "clockscript.js"></script>   </head>	   <body>      Черновик по JavaScript. Работа с холстом: <br> 	 <div id='clock'>Тут будет текущее время</div> <br> 	 <canvas height='480' width='480' id='myCanvas'></canvas>   </body> </html> 

Файл style.css:

#clock{   font-family:Tahoma, sans-serif;   font-size:20px;   font-weight:bold;   color:#0000cc;	 } 

Файл clockscript.js:

window.onload = function(){    window.setInterval( 	function(){ 	    var d = new Date(); 	    document.getElementById("clock").innerHTML = d.toLocaleTimeString(); 	}   , 1000); } 

Разберемся с работой clockscript.js:

Выполняем внутренний JavaScript-код при помощи привязки к событию onload корневого объекта Window:

window.onload = function(){/*бла-бла-бла*/} 

Метод объекта объекта Window, который выполняет код через определенные промежутки времени (указанные в миллисекундах):

window.setInterval(function(){/*Тут действия, обернутые в функцию, которую нужно выполнять каждые 1000 миллисекунд*/} , 1000); 

Объект Date используется для проведения различных манипуляций с датой и временем. С помощью конструктора создаем его экземпляр и называем d:

var d = new Date(); 

Находим объект DOM по его id. Это именно тот объект, в который мы хотим выводить наше время. Это может быть параграф, заголовок или еще какой-то элемент. У меня это div-блок. После получения элемента по id, используем его свойство innerHTML для получение всего содержимого элемента вместе с разметкой внутри. И передаем туда результат метода toLocaleTimeString(), который возвращает форматированное представление времени:

document.getElementById("clock").innerHTML = d.toLocaleTimeString(); 

Вот, что должно получиться(время динамически изменяется каждую секунду):

Реализация аналоговых часов

С этого момента мы будем использовать Canvas (HTML), который будет служить нам холстом для творчества.

Чтобы увидеть наш холст в файле index.html внутри body мы должны где-то расположить следующий тег, сразу определив его размеры:

<canvas height='480' width='480' id='myCanvas'></canvas> 

Теперь в файле clockscript.js, прежде чем пытаться рисовать, нужно получить контекст объекта Canvas. Сделаем это в начале нашей функции отображения часов. Тогда файл clockscript.js изменится следующим образом:

function displayCanvas(){     var canvasHTML = document.getElementById('myCanvas');     var contextHTML = canvasHTML.getContext('2d');     contextHTML.strokeRect(0,0,canvasHTML.width, canvasHTML.height);     //Тут будет вся логика часов и код отображения через графические примитивы     return; }  window.onload = function(){    window.setInterval( 	function(){ 	    var d = new Date(); 	    document.getElementById("clock").innerHTML = d.toLocaleTimeString();             displayCanvas(); 	}   , 1000); } 

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

Угол поворота всех стрелок за 1 секунду:

  • Секундная стрелка повернется на угол — (1/60)*360o = 6o
  • Минутная стрелка повернется на угол — (1/60)*6o = 0,1o
  • Часовая стрелка повернется на угол — (1/60)*0,1o ≈ 0,0017o

Первая проблема:

То есть даже за 1 секунду все стрелки должны повернуться, каждая на соответствующий угол. И если это не учесть, то первый подводный камень, который мы получим в отображении, будет некрасивая анимация. К примеру, когда время будет 19:30, то часовая стрелка будет ровно показывать на 19 часов, хотя в реальной жизни она должна уже быть наполовину приближена к 20 часам. Аналогично, приятнее будет выглядеть плавное передвижение минутной стрелки. Ну а секундная стрелка пусть перещелкивается дискретными движениями, как в большинстве реальных механических часов. Решение проблемы: прибавлять к углы поворота текущей стрелки угол поворота более быстрой стрелки, домноженный на коэффициент, обозначающий его долю от угла текущей стрелки.

Реализация:

var t_sec = 6*d.getSeconds();  //Определяем угол для секунд var t_min = 6*(d.getMinutes() + (1/60)*d.getSeconds()); //Определяем угол для минут var t_hour = 30*(d.getHours() + (1/60)*d.getMinutes());  //Определяем угол для часов 

Вторая проблема:

Угол вращающегося радиус-вектора(стрелки часов) отсчитывается от положительного направления в направлении против часовой стрелки. Если мы это не учтем в нашей логике, то направим часы назад в прошлое.

И еще, отсчет часов, минут и секунд у нас происходит от цифры 12, верхнего положения. Решение проблемы: в наших формулах мы должны учесть это в качестве сдвига +π/2 (90o). А перед значением угла ставить знак "-", чтобы часы шли именно по часовой стрелке. И, конечно, учитывать, что передача угла в градусах в тригонометрические функции языков программирования осуществляется с умножением на коэффициент "π/180o".

Реализация на примере секундной стрелки:

contextHTML.moveTo(xCenterClock, yCenterClock); contextHTML.lineTo(xCenterClock + lengthSeconds*Math.cos(Math.PI/2 - t_sec*(Math.PI/180)),                      yCenterClock - lengthSeconds*Math.sin(Math.PI/2 - t_sec*(Math.PI/180))); 

Третья проблема:

В ходе разметки рисочек циферблата нужно как-то выделить рисочки напротив часов. Всего рисочек — 60 для секунд и минут. 12 — для часов. Эти 12 должны как-то выделяться на фоне всех остальных. Также симметричность оцифровки зависит от ширины цифр. Очевидно, что цифры 10, 11 и 12 шире, чем 1, 2, 3 и т.д. Про это нужно не забыть.

Решение проблемы и вариант оцифровки циферблата:

for(var th = 1; th <= 12; th++){ 	contextHTML.beginPath(); 	contextHTML.font = 'bold 25px sans-serif'; 	var xText = xCenterClock + (radiusNum - 30) * Math.cos(-30*th*(Math.PI/180) + Math.PI/2); 	var yText = yCenterClock - (radiusNum - 30) * Math.sin(-30*th*(Math.PI/180) + Math.PI/2); 	//Следующий кусок когда учитывает то, что симметричность оцифровки зависит от ширины цифры 	//Начиная с "10" часов это нужно корректировать(!) 	if(th <= 9){ 	    contextHTML.strokeText(th, xText - 5 , yText + 10); 	}else{ 	    contextHTML.strokeText(th, xText - 15 , yText + 10); 	}      	contextHTML.stroke(); 	contextHTML.closePath();	 } 

С учетом всего этого, мой вариант исполнения кода логики и отображения аналоговых часов выглядит следующим образом:

Код clockscript.js

function displayCanvas(){     var canvasHTML = document.getElementById('myCanvas');     var contextHTML = canvasHTML.getContext('2d');     contextHTML.strokeRect(0,0,canvasHTML.width, canvasHTML.height); 	     //Расчет координат центра и радиуса часов     var radiusClock = canvasHTML.width/2 - 10;     var xCenterClock = canvasHTML.width/2;     var yCenterClock = canvasHTML.height/2; 	     //Очистка экрана.      contextHTML.fillStyle = "#ffffff";     contextHTML.fillRect(0,0,canvasHTML.width,canvasHTML.height); 	     //Рисуем контур часов     contextHTML.strokeStyle =  "#000000";     contextHTML.lineWidth = 1;     contextHTML.beginPath();     contextHTML.arc(xCenterClock, yCenterClock, radiusClock, 0, 2*Math.PI, true);     contextHTML.moveTo(xCenterClock, yCenterClock);     contextHTML.stroke();     contextHTML.closePath(); 	     //Рисуем рисочки часов     var radiusNum = radiusClock - 10; //Радиус расположения рисочек	     var radiusPoint;     for(var tm = 0; tm < 60; tm++){ 	  contextHTML.beginPath(); 	  if(tm % 5 == 0){radiusPoint = 5;}else{radiusPoint = 2;} //для выделения часовых рисочек 	  var xPointM = xCenterClock + radiusNum * Math.cos(-6*tm*(Math.PI/180) + Math.PI/2); 	  var yPointM = yCenterClock - radiusNum * Math.sin(-6*tm*(Math.PI/180) + Math.PI/2); 	  contextHTML.arc(xPointM, yPointM, radiusPoint, 0, 2*Math.PI, true); 	  contextHTML.stroke(); 	  contextHTML.closePath();     }  	     //Оцифровка циферблата часов     for(var th = 1; th <= 12; th++){ 	contextHTML.beginPath(); 	contextHTML.font = 'bold 25px sans-serif'; 	var xText = xCenterClock + (radiusNum - 30) * Math.cos(-30*th*(Math.PI/180) + Math.PI/2); 	var yText = yCenterClock - (radiusNum - 30) * Math.sin(-30*th*(Math.PI/180) + Math.PI/2); 	if(th <= 9){ 		contextHTML.strokeText(th, xText - 5 , yText + 10); 	}else{ 		contextHTML.strokeText(th, xText - 15 , yText + 10); 	}      	contextHTML.stroke(); 	contextHTML.closePath();	     }  	     //Рисуем стрелки     var lengthSeconds = radiusNum - 10;     var lengthMinutes = radiusNum - 15;     var lengthHour = lengthMinutes / 1.5;     var d = new Date();                //Получаем экземпляр даты     var t_sec = 6*d.getSeconds();                           //Определяем угол для секунд     var t_min = 6*(d.getMinutes() + (1/60)*d.getSeconds()); //Определяем угол для минут     var t_hour = 30*(d.getHours() + (1/60)*d.getMinutes()); //Определяем угол для часов 	     //Рисуем секунды     contextHTML.beginPath();     contextHTML.strokeStyle =  "#FF0000";     contextHTML.moveTo(xCenterClock, yCenterClock);     contextHTML.lineTo(xCenterClock + lengthSeconds*Math.cos(Math.PI/2 - t_sec*(Math.PI/180)), 				yCenterClock - lengthSeconds*Math.sin(Math.PI/2 - t_sec*(Math.PI/180)));     contextHTML.stroke();     contextHTML.closePath();      //Рисуем минуты     contextHTML.beginPath();     contextHTML.strokeStyle =  "#000000";     contextHTML.lineWidth = 3;     contextHTML.moveTo(xCenterClock, yCenterClock);     contextHTML.lineTo(xCenterClock + lengthMinutes*Math.cos(Math.PI/2 - t_min*(Math.PI/180)), 				 yCenterClock - lengthMinutes*Math.sin(Math.PI/2 - t_min*(Math.PI/180)));     contextHTML.stroke();     contextHTML.closePath();      //Рисуем часы     contextHTML.beginPath();     contextHTML.lineWidth = 5;     contextHTML.moveTo(xCenterClock, yCenterClock);     contextHTML.lineTo(xCenterClock + lengthHour*Math.cos(Math.PI/2 - t_hour*(Math.PI/180)), 				 yCenterClock - lengthHour*Math.sin(Math.PI/2 - t_hour*(Math.PI/180)));     contextHTML.stroke();     contextHTML.closePath();	 	     //Рисуем центр часов     contextHTML.beginPath();     contextHTML.strokeStyle =  "#000000";     contextHTML.fillStyle = "#ffffff";     contextHTML.lineWidth = 3;     contextHTML.arc(xCenterClock, yCenterClock, 5, 0, 2*Math.PI, true);     contextHTML.stroke();     contextHTML.fill();     contextHTML.closePath(); 	       return; }   window.onload = function(){     window.setInterval( 	function(){ 		var d = new Date(); 		document.getElementById("clock").innerHTML = d.toLocaleTimeString(); 		displayCanvas(); 	}     , 1000); } 

Заключение

Вот такие часы должны получиться в итоге. Надеюсь, что данная статья поможет вам разобраться в элементарной анимации на JavaScript и зародит у вас интерес к освоению этого прекрасного языка программирования. Спасибо за внимание.

ссылка на оригинал статьи http://habrahabr.ru/post/275353/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *