Mřížka
Předtím, než začneme kreslit, si potřebujeme promluvit o mřížce na plátně či o
souřadném prostoru
. HTML šablona z předchozí stránky měla prvek <canvas>
150 pixelů do šířky a 150 pixelů do výšky, obrázek byl vykreslen s výchozím umístěním mřížky. Tato mřížka bývá umístěna do levého horního rohu (souřadnice [0]) a 1 jednotka na ní obvykle odpovídá 1 pixelu na plátně. Všechny jsou umístěny relativně od počátku, takže pozice levého horního rohu na modrém čtverci je 50 x pixelů zleva a y pixelů zeshora (souřadnice [x,y]).
Později v tomto tutoriálu uvidíme, jak můžeme počátek přenést do jiného bodu, otáčet mřížku a dokonce ji i zvětšovat. Prozatím ji ale ponecháme tam, kde je.
Kreslení tvarů
Na rozdíl od SVG, canvas
podporuje pouze jeden základní tvar – obdélníky. Všechny další musí být vytvořeny kombinací jedné či více cest. Naštěstí máme soubor kreslících funkcí cest, které umožní vytvářet tvary i velmi složité.
Obdélníky
Nejprve se podívejme na obdélník. Existují tři funkce, které jej na plátně vykreslí.
fillRect(x,y,sirka,vyska)
: Vykreslí vyplněný obdélník
strokeRect(x,y,sirka,vyska)
: Vykreslí obdélník s obrysem
clearRect(x,y,sirka,vyska)
: Vymaže požadované místo, a učiní ho zcela průhledným
Každá z těchto tři funkcí přijímá stejné parametry. x
a y
určují pozici (vztahující se k počátku) levého horního rohu obdélníku na plátně. sirka
a vyska
jsou jasné. Podívejme se, jak tyto funkce fungují.
Ukážeme si to na funkci kresli()
z předchozí stránky, ke které jsme teď přidali tři výše uvedené funkce.
Příklad obdélníkového tvaru
function kresli(){ var platno = document.getElementById('tutorial'); if (platno.getContext){ var ctx = platno.getContext('2d'); ctx.fillRect(25,25,100,100); ctx.clearRect(45,45,60,60); ctx.strokeRect(50,50,50,50); } }
Výsledek by měl vypadat podobně jako na obrázku vpravo. Funkce fillRect
vykreslí velký černý čtverec 100x100 pixelů, funkce clearRect
odstraní čtverec 60x60 pixelů uprostřed a nakonec funkce strokeRect
vykreslí obdélník s obrysem 50x50 pixelů uvnitř odstraněného čtverce.
Na následujících stránkách uvidíme dva odlišné způsoby užití funkce clearRect
a také uvidíme, jak změnit barvu a styl čáry zobrazených tvarů.
Na rozdíl od funkcí cest v další sekci, všechny tři obdélníkové funkce vykreslují přímo na plátno.
Kreslení cest
K vytvoření tvarů pomocí cest potřebujeme několik zvláštních kroků.
beginPath()
closePath()
stroke()
fill()
První krok k vytvoření cesty je zavolání metody beginPath
. Cesty jsou vnitřně uloženy jako seznam sub-cest (čáry, oblouky, atd…), které společně vytvoří tvar. Při každém volání této metody je tento seznam vymazán a my můžeme začít kreslit nové tvary.
Druhý krok je volání metod, které určí vlastní cesty k vykreslení. To si ukážeme za chvíli.
Třetí a nepovinný krok je volání metody closePath
. Tato metoda zkouší uzavřít tvar vykreslením přímky z aktuálního bodu k počátku. Pokud již byl tvar uzavřen nebo existuje v seznamu jen jedna položka, tato funkce neudělá nic.
Posledním krokem je volání metody stroke
a/nebo fill
. Voláním jedné z nich se v momentě vykreslí tvar na plátno. stroke
je používána k vykreslení tvarů s obrysem, zatímco fill
je používána k malování vyplněných tvarů.
Poznámka: Po zavolání metody fill
budou automaticky uzavřeny všechny otevřené tvary a tak není nezbytné použít metodu closePath
.
Kód pro jednoduché nakreslení tvaru (trojúhelník) by vypadal např. takto:
ctx.beginPath(); ctx.moveTo(75,50); ctx.lineTo(100,75); ctx.lineTo(100,25); ctx.fill();
Metoda moveTo()
Jedna z velmi užitečných funkcí, která ve skutečnosti nic nevykreslí, ale je součástí seznamu cest popsaného výše, je funkce moveTo
. Asi nejlépe si ji můžete představit jako zvednutí pera nebo tužky z jednoho bodu a jeho umístění dál na kousku papíru.
moveTo(x, y)
Metoda moveTo
přijímá dva argumenty (x a y) které jsou souřadnicemi nového počátečního bodu.
Po inicializaci plátna či volání metody beginPath
je počáteční bod nastaven na souřadnici [0]. Ve většině případů bychom použili metodu moveTo
k umístění počátečního bodu někam jinam, ale můžemeji také využít k vykreslení nespojených cest. Podívejte se na usmívající se obličej vpravo – červenou čarou jsou vyznačena místa, kde byla použita metoda moveTo
.
Zkuste si to sami, použít můžete část kódu níže. Stačí jej vložit do funkce kresli
, kterou již známe.
Příklad metody moveTo()
ctx.beginPath(); ctx.arc(75,75,50,0,Math.PI*2,true); // Vnější kružnice ctx.moveTo(110,75); ctx.arc(75,75,35,0,Math.PI,false); // Ústa (ve směru hodinových ručiček) ctx.moveTo(65,65); ctx.arc(60,65,5,0,Math.PI*2,true); // Levé oko ctx.moveTo(95,65); ctx.arc(90,65,5,0,Math.PI*2,true); // Pravé oko ctx.stroke();
Poznámka 1: Po odstranění metody moveTo
budou spojovací čáry viditelné.
Poznámka 2: Popis funkce arc
a jejich parametrů najdete níže.
Čáry
Pro kreslení přímek používáme metodu lineTo
.
lineTo(x, y)
Tato metoda přijímá dva argumenty (x
a y
), které jsou souřadnicemi krajního bodu čáry. Počáteční bod je závislý na předchozích nakreslených cestách, kde krajní bod předešlé cesty je počátečním bodem pro následující. Počáteční bod může být také změněn pomocí metody moveTo
.
Příklad metody lineTo()
V příkladu níže jsou vykresleny dva trojúhelníky, jeden vyplněný a druhý s obrysem (výsledek můžete vidět na obrázku vpravo). Nejprve je volána metoda beginPath
, jež začíná nový tvar. Dále jsou vykresleny dvě čáry, které sestaví dvě stěny trojúhelníku.
Povšimněte si rozdílů mezi plným trojúhelníkem a trojúhelníkem s obrysem. Jak již bylo řečeno, tvary se automaticky uzavřou, je-li cesta vyplněná. Pokud bychom udělali u trojúhelníku s obrysem, vykreslily by se jen dvě čáry, nikoliv úplný trojúhelník.
// Vyplněný trojúhelník ctx.beginPath(); ctx.moveTo(25,25); ctx.lineTo(105,25); ctx.lineTo(25,105); ctx.fill(); // Trojúhelník s obrysem ctx.beginPath(); ctx.moveTo(125,125); ctx.lineTo(125,45); ctx.lineTo(45,125); ctx.closePath(); ctx.stroke();
Oblouky
Pro kreslení oblouků a kružnic používáme metodu arc
. Doporučení také popisuje metodu arcTo
, která je podporována v Safari, ale do nynějších webových prohlížečů postavených na jádře Gecko ještě nebyla implementována.
arc(x, y, radius, pocatecniUhel, konecnyUhel, protismeruHodinovychRucicek)
Tato metoda přijímá pět parametrů: x
a y
jsou souřadnicemi středu kružnice, radius
je poloměrem oblouku, parametry pocatecniUhel
a konecnyUhel
definují počáteční resp. koncový bod oblouku v radiánech. Počáteční i koncový úhel je měřen od osy x. Parametr protismeruHodinovychRucicek
je typu boolean a je-li nastaven na true, vykreslí oblouk proti směru hodinových ručiček, jinak vykresluje po směru.
Varování: V betaverzích Firefoxu 1.5 byl poslední parametr posmeruHodinovychRucicek
. Finální verze ale podporuje funkci tak, jak je výše popsáno a všechny skripty, které používají tuto metodu v zastaralé podobě, je potřeba upravit.
Poznámka: Úhly u funkce arc
jsou měřeny v radiánech a nikoliv ve stupních. Pro převod stupňů na radiány můžete použít následující JavaScriptový výraz: var radians = (Math.PI/180)*stupne
.
Příklad metody arc()
Následující příklad je mnohem složitější než ty předchozí. Vykreslí se 12 různých oblouků s různými uhly a výplní. Kdyby tento příklad měl mít podobu usmívajícího se obličeje výše, vyžádalo by si to větši množství kódu a při kreslení oblouků bychom potřebovali znát každý jednotlivý bod. Pro oblouky o 90, 180 a 270 stupních, jaké jsme použili zde, by to nebyl až takový problém, ale pro složitější oblouky je tento způsob příliš složitý.
Dva for
cykly slouží k procházení přes řádky a sloupce oblouků. Pro každý oblouk začneme novou cestu pomocí metody beginPath
. Všechny parametry jsou rozepsány jako proměnné, aby bylo jasně viditelné, co který znamená – běžně by se jednalo pouze o jeden příkaz. Souřadnice x
a y
by měly být dostatečně srozumitelné radius
a pocatekUhlu
jsou dané, konecnyUhel
začíná na 180 stupních (první sloupec) a je postupně zvyšován o 90 stupňů až do podoby celé kružnice (poslední sloupec). Příkaz pro parametr posmeruHodinovychRucicek
vrátí v prvním a třetím řádku vykreslený oblouk po směru hodinových ručiček, druhý a třetí řádek oblouk v protisměru hodinových ručiček. Nakonec, podmínka if
vykreslí horní polovinu oblouků pouze vytaženou a spodní polovinu vyplněnou.
for (i=0;i<4;i++){ for(j=0;j<3;j++){ ctx.beginPath(); var x = 25+j*50; // x-ová souřadnice var y = 25+i*50; // y-ová souřadnice var radius = 20; // rádius oblouku var pocatecniUhel = 0; // počáteční bod kružnice var konecnyUhel = Math.PI+(Math.PI*j)/2; // konečný bod kružnice var protismeruHodinovychRucicek = i%2==0 ? false : true; // Ve směru hodinových ručiček nebo v protisměru hodinových ručiček ctx.arc(x,y,pocatecniUhel,konecnyUhel,protismeruHodinovychRucicek); if (i>1){ ctx.fill(); } else { ctx.stroke(); } } }
Bézierova a kvadratická křivka
Další typ dostupných cest jsou Bézierovy křivky, k dispozici v kubické a kvadratické variantě. Obvykle jsou používány ke kreslení složitých základních tvarů.
quadraticCurveTo(cp1x, cp1y, x, y) // NEFUNKČNÍ ve Firefoxu 1.5 (níže je uvedeno, jak to obejít)
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
Rozdíl mezi nimi nejlépe vysvětlí obrázek vpravo. Kvadratická Bézierova křivka má počátečný a koncový bod (modré tečky) a jen jeden
řídící bod
(červená tečka), zatímco kubická Bézierova křivka používá řídící body dva.
Parametry x
a y
u obou těchto metod jsou souřadnice konečného bodu, cp1x
a cp1y
jsou souřadnicemi prvního řídícího bodu, cp2x
a cp2y
jsou souřadnicemi druhého řídícího bodu.
Používání kvadratických a kubických Bézierových křivek může být docela složité, protože na rozdíl od vektorových programů jako Adobe Illustrator nebo Inkscape nemáme přímou vizuální odezvu, jak co děláme, a při vykreslování složitých tvarů je to znát. V následujícím příkladu budeme kreslit několik jednoduchých základních tvarů, ale pokud máte čas a především trpělivost, můžete vytvořit mnohem složitější tvary.
V obou případech uvidíme řadu křivek vykreslovaných postupně až dají dohromady výsledný tvar.
Příklad metody quadraticCurveTo()
// Příklad kvadratické křivky ctx.beginPath(); ctx.moveTo(75,25); ctx.quadraticCurveTo(25,25,25,62.5); ctx.quadraticCurveTo(25,100,50,100); ctx.quadraticCurveTo(50,120,30,125); ctx.quadraticCurveTo(60,120,65,100); ctx.quadraticCurveTo(125,100,125,62.5); ctx.quadraticCurveTo(125,25,75,25); ctx.stroke();
Jakoukoliv Bézierovu křivku je možné převést na kubickou Bézierovu křivku, pokud spočítáme oba řídící body kubické Bézierovy křivky od jediného řídícího bodu kvadratické Bézierovy křivky – opačně to však možné není. Přesný převod kubické Bézierovy křivky na kvadratickou Bézierovu křivku je možný pouze pokud je příslušný kubický polynom roven nule. K aproximaci kubické Bézierovy křivky za použití více křivek kvadratických je často užívána metoda půlení intervalu.
Příklad metody bezierCurveTo()
// Příklad Bézierovy křivky ctx.beginPath(); ctx.moveTo(75,40); ctx.bezierCurveTo(75,37,70,25,50,25); ctx.bezierCurveTo(20,25,20,62.5,20,62.5); ctx.bezierCurveTo(20,80,40,102,75,120); ctx.bezierCurveTo(110,102,130,80,130,62.5); ctx.bezierCurveTo(130,62.5,130,25,100,25); ctx.bezierCurveTo(85,25,75,37,75,40); ctx.fill();
Řešení chyby v metodě quadraticCurveTo()
ve Firefoxu 1.5
V implementaci quadatricCurveTo()
ve Firefoxu 1.5 je chyba, kvůli níž tato metoda nevykreslí kvadratickou křivku, ale pouze zavolá stejnou funkci jako bezierCurveTo()
, která vrací dvakrát stejný řídící bod se souřadnicemi [x, y]. quadraticCurveTo()
z tohoto důvodu vrací nesprávné výsledky. Pokud potřebujeme použít quadraticCurveTo(), musíme převést kvadratickou Bézierovu křivku na kubickou Bézierovu křivku sami za použití metody quadraticCurveTo()
, která funguje v pořádku.
var aktualniX, aktualniY; // nastaví poslední souřadnici x,y a předá je funkcím lineTo/moveTo/bezierCurveTo nebo quadraticCurveToFixed() function kvadratickaKrivkaOpravena(rbx, rby, x, y) { /* Pro rovnice níže jsou použity následující proměnné zkratky: kb0 je počáteční bod kvadratické křivky (musíme uchovávat váš poslední bod zaslaný moveTo(), lineTo() nebo bezierCurveTo()). kb1 je řídící bod kvadratické křivky (to jsou kbx, kby, které bychom poslali quadraticCurveTo()) kb2 je konečný bod kvadratické křivky (to jsou argumenty x, y, které bychom poslali quadraticCurveTo()) Převedeme tyto body spočítáním dvou potřebných kubických řídicích bodů (počáteční/konečné body, které jsou stejné pro obě kvadratické a kubické křivky). Rovnice pro dva kubické řídící body jsou: rb0 = kb0 a rb3 = kb2 rb1 = kb0 + 2/3 *(kb1-kb0) rb2 = rb1 + 1/3 *(kb2-kb0) V následujícím kódu musíme spočítat oba x a y výrazy pro každý bod zvlášť. rb1x = kb0x + 2.0/3.0*(kb1x - kb0x); rb1x = kb0y + 2.0/3.0*(kb1y - kb0y); rb2x = rb1x + (kb2x - kb0x)/3.0; rb2y = rb1y + (kb2y - kb0y)/3.0; Nyní budeme a) nahrazovat proměnné kb0x a kb0y s aktualniX a aktualniY (které musíme uchovávat pro každou funkci moveTo/lineTo/bezierCurveTo). b) nahrazovat proměnné kb1x a kb1y s rbx a rby (které bychom předali quadraticCurveTo()). c) nahrazovat proměnné kb2x a kb2y s x a y a tak dostáváme: */ var cp1x = aktualniX + 2.0/3.0*(rbx - aktualniX); var cp1x = aktualniY + 2.0/3.0*(rby - aktualniY); var cp2x = rb1x + (x - aktualniX)/3.0; var cp2y = rb1y + (y - aktualniY)/3.0; // nyní zavoláme kubickou Bézierovu křivku bezierCurveTo(rb1x, rb1y, rb2x, rb2y, x, y ); aktualniX = x; aktualniY = y; }
Obdélníky
Kromě tří metod, které jsme viděli výše a které vykreslí obdélníkové tvary přímo na plátno, existuje také metoda rect
, která přidá cestu obdélníku do seznamu cest.
rect(x, y, sirka, vyska)
Tato metoda přijímá čtyři argumenty. Parametry x
a y
definuji souřadnici levého horního rohu nové cesty obdélníku, sirka
a vyska
definují šířku a výšku obdélníku.
Při volání této metody je automaticky zavolána metoda moveTo
s parametry [0], tj. počáteční bod se vrací do počátku souřadného systému.
Vytváření kombinací
Ve všech příkladech na této stránce jsme k vykreslení křivky použili vždy jen jeden typ cesty. Žádné omezení v množství nebo typech cest, které můžete použít k vytvoření tvaru, však neexistuje. V posledním příkladě se pokusíme zkombinovat všechny funkce cest ke sestavení jedné velmi známé herní postavičky.
Příklad
Nejdůležitější věci k povšimnutí jsou funkce zakulacenyObdelnik
a použití vlastnosti fillStyle
. Definováním vašich vlastních funkcí k vykreslení velmi složitých tvarů může být užitečné a ušetří váš čas. Nebýt toho, byl by tento skript pravděpodobně dvakrát delší, než je.
Na vlastnost fillStyle
se zaměříme později v tomhle tutoriálu. Zde ji používáme ke změně barvy výplně z výchozí černé na bílou a opačně.
function kresli() { var ctx = document.getElementById('platno').getContext('2d'); zakulacenyObdelnik(ctx,12,12,150,150,15); zakulacenyObdelnik(ctx,19,19,150,150,9); zakulacenyObdelnik(ctx,53,53,49,33,10); zakulacenyObdelnik(ctx,53,119,49,16,6); zakulacenyObdelnik(ctx,135,53,49,33,10); zakulacenyObdelnik(ctx,135,119,25,49,10); ctx.beginPath(); ctx.arc(37,37,13,Math.PI/7,-Math.PI/7,true); ctx.lineTo(31,37); ctx.fill(); for(i=0;i<8;i++){ ctx.fillRect(51+i*16,35,4,4); } for(i=0;i<6;i++){ ctx.fillRect(115,51+i*16,4,4); } for(i=0;i<8;i++){ ctx.fillRect(51+i*16,99,4,4); } ctx.beginPath(); ctx.moveTo(83,116); ctx.lineTo(83,102); ctx.bezierCurveTo(83,94,89,88,97,88); ctx.bezierCurveTo(105,88,111,94,111,102); ctx.lineTo(111,116); ctx.lineTo(106.333,111.333); ctx.lineTo(101.666,116); ctx.lineTo(97,111.333); ctx.lineTo(92.333,116); ctx.lineTo(87.666,111.333); ctx.lineTo(83,116); ctx.fill(); ctx.fillStyle = "white"; ctx.beginPath(); ctx.moveTo(91,96); ctx.bezierCurveTo(88,96,87,99,87,101); ctx.bezierCurveTo(87,103,88,106,91,106); ctx.bezierCurveTo(94,106,95,103,95,101); ctx.bezierCurveTo(95,99,94,96,91,96); ctx.moveTo(103,96); ctx.bezierCurveTo(100,96,99,99,99,101); ctx.bezierCurveTo(99,103,100,106,103,106); ctx.bezierCurveTo(106,106,107,103,107,101); ctx.bezierCurveTo(107,99,106,96,103,96); ctx.fill(); ctx.fillStyle = "black"; ctx.beginPath(); ctx.arc(101,102,2,0,Math.PI*2,true); ctx.fill(); ctx.beginPath(); ctx.arc(89,102,2,0,Math.PI*2,true); ctx.fill(); } function zakulacenyObdelnik (ctx,x,y,sirka,vyska,radius){ ctx.beginPath(); ctx.moveTo(x,y+radius); ctx.lineTo(x,y+vyska-radius); ctx.quadraticCurveTo(x,y+vyska,x+radius,y+vyska); ctx.lineTo(x+sirka-radius,y+vyska); ctx.quadraticCurveTo(x+sirka,y+vyska,x+sirka,y+vyska-radius); ctx.lineTo(x+sirka,y+radius); ctx.quadraticCurveTo(x+sirka,y,x+sirka-radius,y); ctx.lineTo(x+radius,y); ctx.quadraticCurveTo(x,y,x,y+radius); ctx.stroke(); }