Michal Hanuš
<web developer>
© 24.11.2011 - Michal Hanuš
Vytvořím animované analogové hodiny kreslené JavaScriptem do elementu canvas. Výsledná animace bude v elementu zarovnána na střed. Velikost hodin se sama přizpůsobí velikosti plátna bez nutnosti upravovat zdrojový kód. Hodiny budou zobrazovat námi zvolený čas.
<canvas id="canvas_hodiny" width="640" height="250"></canvas>
Takto vypadá zápis plátna v HTML5 v té nejjednodušší podobě. V zápisu jsem definoval identifikátor, šířku a výšku plátna. Díky identifikátoru snadno najdeme naše plátno v JavaScriptu a zároveň jej můžeme stylovat pomocí CSS.
#canvas_hodiny{
border: 1px solid #DCDCDC;
padding: 10px;
}
Použíté stylování uvádím jen pro úplnost. Tato část nemá žádný vliv na výsledné kreslení na plátno.
function nakresliHodiny(casServer,casPC){
var context = document.getElementById('canvas_hodiny').getContext('2d');
…
}
Pro kreslení na plátno jsem vytvořil funkci “nakresliHodiny” se dvěma parametry. První parametr nese časové razítko pro zobrazení libovolného času, v našem případě použiji serverový čas pomocí PHP. Druhým parametrem bude lokální čas v době volání funkce. Díky tomu budeme později sledovat, jak se čas mění. V úvodu funkce najdeme naše plátno a definujeme mu plochu ve 2D.
var sirka = document.getElementById('canvas_hodiny').width;
var vyska = document.getElementById('canvas_hodiny').height;
if(sirka<vyska) var polomer = sirka/2;
else var polomer = vyska/2;
context.clearRect(0, 0, sirka, vyska);
V této části zjistíme skutečnou šířku a výšku plátna. Pomocí podmínky na třetím řádku si určíme poloměr hodin. Tato hodnota je velmi důležitá, protože bude ovlivňovat velikost všech obrazců na plátně. Na posledním řádku vymažeme celou plochu plátna. Díky tomu budeme vykreslovat každý frame na čisté plátno.
if(sirka<vyska){
var okrajTop = (vyska-sirka)/2;
var okrajLeft = 0;
}
else{
var okrajTop = 0;
var okrajLeft = (sirka-vyska)/2;
}
Pokud se šířka nebude rovnat výšce plátna, vznikne nám místo kolem hodin, do kterého kreslit nebudeme. Tyto okraje použijeme při práci s barvami v následující části.
var barvaRam = context.createLinearGradient(polomer+okrajLeft, okrajTop, polomer+okrajLeft, (polomer*2)+okrajTop);
barvaRam.addColorStop(0, '#dc0032');
barvaRam.addColorStop(1, '#6d0019');
var barvaCifernik = context.createLinearGradient(polomer+okrajLeft, okrajTop, polomer+okrajLeft, (polomer*2)+okrajTop);
barvaCifernik.addColorStop(0, '#ffffff');
barvaCifernik.addColorStop(1, '#ededed');
var barvaStred = context.createLinearGradient(polomer+okrajLeft, polomer+okrajTop-polomer*0.035, polomer+okrajLeft, polomer+okrajTop+polomer*0.035);
barvaStred.addColorStop(0, '#7d7e7d');
barvaStred.addColorStop(0.5, '#0e0e0e');
Zde si definujeme barevný přechod pro rám hodin, pozadí ciferníku a barvu středu. Pomocí dříve definovaných okrajů zde přesně určíme, odkud kam barevný přechod povede.
context.beginPath();
context.arc(sirka/2, vyska/2, polomer, 0, (Math.PI/180)*360, true);
context.closePath();
context.fillStyle = barvaRam;
context.fill();
Nyní konečně přejdeme k samotnému kreslení. Těchto pět řádků nám nakreslí rám hodin s červeně zbarveným přechodem.
context.lineWidth = 1;
context.beginPath();
context.arc(sirka/2, vyska/2, polomer-(polomer*2*0.04), 0, (Math.PI/180)*360, true);
context.closePath();
context.fillStyle = barvaCifernik;
context.fill();
context.strokeStyle = 'rgba(0,0,0,0.5)';
context.stroke();
Na prvním řádku nastavíme sílu čáry na 1px. Nakreslíme základ ciferníku, kde za povšimnutí stojí práce s poloměrem. V podstatě tak rozměry zadáváme v poměru k celým hodinám. Díky tomu se hodiny automaticky přizpůsobí velikosti plátna.
context.beginPath();
context.save();
context.translate(sirka/2, vyska/2);
if(polomer>50){
for(i=1;i<=60;i++){
context.rect(polomer*-0.03+polomer-polomer*2*0.08, -1, polomer*2*0.03, 2);
context.rotate((Math.PI/180)*6);
}
}
for(i=1;i<=12;i++){
context.rect(polomer*-0.03+polomer-polomer*2*0.08, polomer*-0.03, polomer*2*0.03, polomer*2*0.03);
context.rotate((Math.PI/180)*30);
}
context.restore();
context.closePath();
context.fillStyle = '#2d2d2d';
context.fill();
První cyklus nám vykreslí 60 minutových bodů. Tento cyklus je omezen podmínkou, která zabrání vykreslení těchto bodů, pokud bude průměr hodin menší než 100px. Důvodem je jen to, že při tak malých rozměrech vypadá ciferník příliš přeplácaně. Druhý cyklus nám vykreslí 12 hodinových bodů.
context.beginPath();
context.save();
context.translate(sirka/2, vyska/2);
context.rotate((Math.PI/180)*-60);
context.font = polomer*0.2+'px Tahoma';
context.textBaseline = 'middle';
for(i=1;i<=12;i++){
context.translate(polomer*-0.03+polomer-polomer*2*0.08-polomer*2*0.07, 0);
context.rotate((Math.PI/180)*(90-i*30));
context.fillText(i, 0, 0);
context.rotate((Math.PI/180)*-(90-i*30));
context.translate(-(0-polomer*0.03+polomer-polomer*2*0.08-polomer*2*0.07), 0);
context.rotate((Math.PI/180)*30);
}
context.restore();
context.closePath();
Zde příjde na řadu trochu složitější cyklus, který vykreslí čísla hodin k hodinovým bodům. Zajímavá je zde práce s rotacemi plátna, díky které jsou čísla na správném místě nasměrována správným směrem.
var cas = new Date();
cas.setTime(casServer*1000+cas.getTime()-casPC);
var hodinovka = cas.getHours();
var minutovka = cas.getMinutes();
var sekundovka = cas.getSeconds();
Na řadu přichází práce s časem, abychom později mohli směrovat hodinové ručičky do sprváných bodů. Na prvním řádku zjišťujeme aktuální lokální čas. Následuje vzoreček, který určí o kolik se zvýšil serverový čas od prvního volání funkce. Dále si definujeme zvlášť hodiny, minuty a sekundy.
context.lineWidth = 2;
context.lineJoin = 'round';
context.beginPath();
context.save();
context.translate(sirka/2, vyska/2);
context.rotate((Math.PI/180)*-90);
context.rotate((Math.PI/180)*(hodinovka*30));
context.rotate((Math.PI/180)*(minutovka*0.5));
context.moveTo(polomer*-0.15, polomer*-0.025);
context.lineTo(0, polomer*-0.04);
context.lineTo(polomer*0.5, 0);
context.lineTo(0, polomer*0.04);
context.lineTo(polomer*-0.15, polomer*0.025);
context.lineTo(polomer*-0.2, polomer*0.013);
context.lineTo(polomer*-0.21, 0);
context.lineTo(polomer*-0.2, polomer*-0.013);
context.restore();
context.closePath();
context.strokeStyle = '#57000c';
context.stroke();
context.fillStyle = '#57000c';
context.fill();
V následujících třech částech se vykreslí jednotlivé ručičky. Základní je opět práce s rotacemi, díky nímž směrujeme ručičky správným směrem. Pak v podstatě jen kreslíme čáry utvářející výsledný tvar ručiček.
context.beginPath();
context.save();
context.translate(sirka/2, vyska/2);
context.rotate((Math.PI/180)*-90);
context.rotate((Math.PI/180)*(minutovka*6));
context.rotate((Math.PI/180)*(sekundovka*0.1));
context.moveTo(polomer*-0.15, polomer*-0.025);
context.lineTo(0, polomer*-0.03);
context.lineTo(polomer*0.75, 0);
context.lineTo(0, polomer*0.03);
context.lineTo(polomer*-0.15, polomer*0.025);
context.lineTo(polomer*-0.23, polomer*0.013);
context.lineTo(polomer*-0.26, 0);
context.lineTo(polomer*-0.23, polomer*-0.013);
context.restore();
context.closePath();
context.strokeStyle = '#8f0014';
context.stroke();
context.fillStyle = '#8f0014';
context.fill();
U hodinové a minutové ručičky bychom neměli zapomenout na drobné posuny “navíc”. Myslím tím, že například v 9:45 by hodinová ručička neměla stále mířit na devítku, ale měla by již být blíže k desítce.
context.beginPath();
context.save();
context.translate(sirka/2, vyska/2);
context.rotate((Math.PI/180)*-90);
context.rotate((Math.PI/180)*(sekundovka*6));
context.moveTo(polomer*-0.3, polomer*-0.013);
context.lineTo(polomer*0.75, 0);
context.lineTo(polomer*-0.3, polomer*0.013);
context.restore();
context.closePath();
context.strokeStyle = '#000000';
context.stroke();
context.fillStyle = '#000000';
context.fill();
Poslední sekundová ručička.
context.lineWidth = polomer*0.015;
context.beginPath();
context.arc(sirka/2, vyska/2, polomer*0.035, 0, (Math.PI/180)*360, true);
context.closePath();
context.fillStyle = barvaStred;
context.fill();
context.stroke();
Nakonec vykreslíme malý kruh s tmavým barevným přechodem a černým okrajem. V tuto chvíli uzavřeme celou funkci.
var casServer = <?=time()?>;
var casPC = (new Date()).getTime();
window.addEventListener('load',nakresliHodiny(casServer,casPC),true);
setTimeout("nakresliHodiny(casServer,casPC)",1000);
Tyto poslední čtyři řádky slouží k animování naší funkce. Na prvním řádku definuji serverový čas pomocí PHP. Na druhém řádku zjišťujeme čas v době načtení stránky. Třetí řádek nám poprvé hodiny vykreslí. Poslední řádek zajistí, aby se plátno překreslilo každou sekundu.
Dle zadání jsem vytvořil analogové hodiny kreslené JavaScriptem do plátna HTML5. Hodiny se samy přizpůsobí velikosti plátna a mohou pracovat s libovolným časem. Na vytvořený kód se vztahuje autorský zákon. Výsledný kód není možné upravovat ani dále šířit bez písemného svolení autora. Věřím však, že výše uvedené kódy poslouží ke studijním účelům pro pochopení jednotlivých logických postupů.