Navigace: Domů Dive Into HTML5

Říkejme tomu (plocha na) kreslení

 

Jdeme na to

HTML 5 definuje element <canvas> jako „bitmapové kreslící plátno závislé na rozlišení, které může být za běhu využíváno pro vykreslování grafů, herní grafiky a ostatních vizuálních prvků“. Canvas je obdélník, umístěný na stránce, ve kterém můžete prostřednictvím JavaScriptu vykreslovat cokoliv se vám zlíbí.

Základní podpora elementu <canvas>
IEFirefoxSafariChromeOperaiPhoneAndroid
7.0+*3.0+3.0+3.0+10.0+1.0+1.0+
* Internet Explorer 7 a 8 vyžadují explorercanvas knihovny třetích stran. Internet Explorer 9 podporuje <canvas> nativně.

Jak vlastně takový canvas vypadá? Nijak. Vážně. Element <canvas> nemá obsah a ani vlastní border.

 ↜ Neviditelný canvas

Kód vypadá přibližně takhle:

<canvas width="300" height="225"></canvas>

Pojďme přidat tečkovaný okraj, ať vidíme s čím se potýkáme.

 ↜ Canvas s okrajem

Na jedné stránce můžete mít více elementů <canvas>. Každý canvas se zobrazí v DOMu a každý canvas si udržuje svůj vlastní stav. Přiřadíte-li elementu canvas atribut id, můžete k němu přistupovat stejně jako ke kterémukoliv jinému elementu.

Pojďme kód rozšířit o atribut id:

<canvas id="a" width="300" height="225"></canvas>

Odteď můžete v DOMu element <canvas> snadno najít.

var a_canvas = document.getElementById("a");

Základní tvary

IEFirefoxSafariChromeOperaiPhoneAndroid
7.0+*3.0+3.0+3.0+10.0+1.0+1.0+
* Internet Explorer 7 a 8 vyžadují explorercanvas knihovny třetích stran. Internet Explorer 9 podporuje <canvas> shapes nativně.

Každé plátno je na začátku prázdné. A to je nuda! Pojďme něco nakreslit.

 ⇜ Klikněte pro kreslení na toto „plátno“

Událost onclick spustí tuto funkci:

function draw_b() {
  var b_canvas = document.getElementById("b");
  var b_context = b_canvas.getContext("2d");
  b_context.fillRect(50, 25, 150, 100);
}

První řádek funkce není nic zvláštního, zkrátka jen najde element <canvas> v DOMu.

A potom je tu tohle  

function draw_b() {
  var b_canvas = document.getElementById("b");
  var b_context = b_canvas.getContext("2d");
  b_context.fillRect(50, 25, 150, 100);
}

muž kreslící před zrcadlem

Každé plátno má svoje kreslicí prostředí (drawing context), kde se odehrává všechna zábava. Jakmile v DOMu najdete element <canvas> (pomocí document.getElementById() nebo jiným způsobem), zavoláte jeho metodu getContext(). Metodě getContext() musíte předat řetězec "2d".

Otázka: Existuje i 3D canvas?
Odpověď: Zatím ne. Někteří výrobci prohlížečů experimetovali s vlastními API pro 3D canvas, ale žádné zatím nebylo standardizováno. Specifikace HTML5 zmiňuje, že „budoucí verze této specifikace pravděpodobně budou definovat i 3D prostředí“.

Takže, máme element <canvas> a máme jeho kreslicí prostředí. V tomto prostředí jsou definovány všechny vlastnosti a metody kreslení. Kreslení obdélníků se věnuje celá skupina vlastností a metod:

Zeptejte se profesora Značky

Otázka: Můžu canvas „zresetovat“?
Odpověď: Ano. Přenastavení rozměrů elementu <canvas> vymaže všechen jeho obsah a vynuluje všechny vlastnosti kreslicího prostředí na základní hodnoty. Rozměry nemusíte ani změnit, stačí je nastavit na jejich současné hodnoty, například takto:

var b_canvas = document.getElementById("b");
b_canvas.width = b_canvas.width;

Pojďme zpět k ukázce kódu z předchozího příkladu…

Nakresli obdélník⇝ 

var b_canvas = document.getElementById("b");
var b_context = b_canvas.getContext("2d");
b_context.fillRect(50, 25, 150, 100);

Zavolání metody fillRect() nakreslí obdélník s nastaveným stylem výplně (tj. černou, dokud ji nenastavíte jinak). Obdélník je ohraničen horním levým rohem (souřadnice 50, 25), jeho šířkou (150) a jeho výškou (100). Pojďme si představit systém souřadnic, abychom lépe pochopili o co jde.

Souřadnice plátna

Canvas je vlastně dvojrozměrná mřížka. Souřadnice (0, 0) jsou levý horní roh plátna. Čím je hodnota souřadnice X vyšší, tím více se po plátně posouváme směrem k pravému rohu. Na ose Y se zvýšením hodnoty přibližujeme ke spodnímu okraji plátna.

Diagram souřadnic plátna

Tento diagram souřadnic byl nakreslen pomocí elementu <canvas>. Skládá se ze:

Nejprve potřebujeme nadefinovat samotný <canvas> element. Element <canvas> definuje šířka a výška, a id abychom jej mohli později nalézt.

<canvas id="c" width="500" height="375"></canvas>

Potom potřebujeme skript, kterým najdeme element <canvas> v DOMu a dostaneme se k jeho kreslicímu prostředí.

var c_canvas = document.getElementById("c");
var context = c_canvas.getContext("2d");

Nyní můžeme začít kreslit čáry.

Cesty

IEFirefoxSafariChromeOperaiPhoneAndroid
7.0+*3.0+3.0+3.0+10.0+1.0+1.0+
* Internet Explorer 7 a 8 vyžadují explorercanvas knihovny třetích stran. Internet Explorer 9 podporuje cesty v <canvas> nativně.

pískomil sedící na židli s psacím brkem a inkoustem

Představte si, že kreslíte obrázek inkoustem. Určitě se do toho nepustíte hned po hlavě, protože můžete inkoustem udělat nevratné chyby. Místo toho načrtnete čáry a křivky tužkou a jakmile s nimi budete spokojeni, obtáhnete svoji skicu inkoustem.

Každé plátno má cestu. Definovat cestu je jako kreslit tužkou. Můžete kreslit co chcete, ale nebude to součástí finálního díla, dokud nevezmete pero a neobtáhnete cestu inkoustem.

K nakreslení rovných čar tužkou můžete použít následující dvě metody:

  1. moveTo(x, y) přesune tužku do specifikovaného počátečního bodu.
  2. lineTo(x, y) nakreslí čáru do specifikovaného bodu.

Čím více budete moveTo() a lineTo() volat, tím větší cesta bude. Toto jsou metody pro „práci s tužkou“ — můžete je volat, jak často chcete, ale na plátně neuvidíte nic, dokud nezavoláte některou z metod pro „práci s inkoustem“.

Začněme s nakreslením šedé mřížky.

for (var x = 0.5; x < 500; x += 10) {
  context.moveTo(x, 0);
  context.lineTo(x, 375);
}

 ⇜ Nakreslí vertikální čáry

for (var y = 0.5; y < 375; y += 10) {
  context.moveTo(0, y);
  context.lineTo(500, y);
}

 ⇜ Nakreslí horizontální čáry

To jsou ještě stále metody „tužky“. Na plátně ještě není nic nakresleno. Potřebujeme metody pro „práci s inkoustem“ abychom čáru navždy obtáhli.

context.strokeStyle = "#eee";
context.stroke();

stroke() je jedna z „inkoustových“ metod. Vezme komplexní cestu, kterou jste definovali se všemi těmi moveTo() a lineTo() voláními, a vykreslí ji na plátno. Barvu čar nastavuje strokeStyle. Toto je výsledek:

Zeptejte se profesora Značky

Otázka: Proč si začal souřadnice x i y v bodu 0,5? Proč ne 0?
Odpověď: Představte si každý pixel jako velký čtverec. Celá čísla souřadnic (0, 1, 2…) jsou rohy těchto čtverců. Pokud mezi celočíselnými souřadnicemi nakreslíte čáru o tloušťce jedné jednotky, přesáhne pixel po stranách, a výsledná čára bude dva pixely široká. Abyste nakreslili pouze pixel širokou linku, musíte změnit souřadnice o 0,5 kolmo ke směru čáry.

Pokud například zkusíte nakreslit čáru z (1, 0)do (1, 3), prohlížeč vykreslí čáru pokrývající 0,5 obrazového pixelu na každé straně, kde se x=1. Obrazovka nemůže zobrazit polovinu pixelu, proto čáru rozšíří na celé dva pixely:

Čára z (1,0) do (1,3) je nakreslena 2 pixely široká

Pokud ale zkusíte nakreslit čáru z (1.5, 0) do (1.5, 3), prohlížeč vykreslí čáru pokrývající 0,5 obrazového pixelu na každé straně, kde x=1,5, čímž vznikne právě jeden pixel široká čára:

Čára z (1.5,0) do (1.5,3) je nakreslena 1 pixel široká

Díky Jason Johnsonovi za poskytnutí těchto diagramů.

Pojďme nakreslit horizontální šipku. Všechny čáry a křivky v cestě jsou vykresleny stejnou barvou (nebo přechodem — ano, už brzy se k nim dostaneme). Šipku chceme nakreslit jinou barvou — černou namísto šedé — takže musíme začít novou cestu.

Nová cesta

context.beginPath();
context.moveTo(0, 40);
context.lineTo(240, 40);
context.moveTo(260, 40);
context.lineTo(500, 40);
context.moveTo(495, 35);
context.lineTo(500, 40);
context.lineTo(495, 45);

Vertikální šipka vypadá skoro stejně. Vzhledem k tomu, že má horizontální i vertikální šipka stejnou barvu, nemusíme vytvářet další cestu. Obě šipky budou součástí jedné cesty.

context.moveTo(60, 0);
context.lineTo(60, 153);
context.moveTo(60, 173);
context.lineTo(60, 375);
context.moveTo(65, 370);
context.lineTo(60, 375);
context.lineTo(55, 370);

 ↜ Bez nové cesty

Říkal jsem, že tyto šipky budou černé, ale strokeStyle je stále nastaven na šedou (fillStyle a strokeStyle nejsou po vytvoření nové cesty vynulovány). To je v pořádku, protože jsme zatím použili jen metody „tužky“. Než obraz vykreslíme doopravdy, „inkoustem“, potřebujeme změnit strokeStyle na černou. V opačném případě se tyto šipky vykreslí šedou, takže je skoro neuvidíme! Následující řádky změní barvu na černou a vykreslí cesty na plátno:

context.strokeStyle = "#000";
context.stroke();

Toto je výsledek:

Text

IEFirefoxSafariChromeOperaiPhoneAndroid
7.0+*3.0+3.0+3.0+10.0+1.0+1.0+
* Internet Explorer 7 a 8 vyžadují explorercanvas knihovny třetích stran. Internet Explorer 9 podporuje canvas text nativně.
† Mozilla Firefox 3.0 pro kompatibilitu vyžaduje záplaty.

Kromě kreslení čar můžete na plátno vykreslovat i text. Narozdíl od textu na této stránce není k dispozici box model, nemůžeme tedy využívat známých CSS technik pro rozvržení stránky: žádný floats, margins, padding nebo word wrapping. (Možná si myslíte, že je to dobře!) Můžete nastavit pár atributů písma, poté zvolíte bod na plátně a text vykreslíte.

V rámci kreslicího prostředí jsou k dispozici následující atributy fontu:

textBaseline je ošidný, protože text je ošidný (na plátno můžete vykreslit kterýkoliv Unicode znak… a Unicode je ošidný). HTML5 specifikace vysvětluje rozdíly účaří:

Výška čtverčíku je přibližně shodná s výškou glyfů, the hanging baseline is where some glyphs like are anchored, the middle is half-way between the top of the em square and the bottom of the em square, the alphabetic baseline is where characters like Á, ÿ, f, and Ω are anchored, the ideographic baseline is where glyphs like and are anchored, and the bottom of the em square is roughly at the bottom of the glyphs in a font. The top and bottom of the bounding box can be far from these baselines, due to glyphs extending far outside the em square.

diagram of different values of the textBaseline property

Pro jednoduché abecedy, jako je například anglická, můžete pro vlastnost textBaseline s klidem užívat hodnoty top, middle nebo bottom.

Pojďme nakreslit nějaký text! Text vykreslený „na plátno“ dědí velikost a styl písma definovaný pro element <canvas> samotný, ale toto nastavení můžete snadno obejít, nastavíte-li vlastnost font v kreslicím prostředí.

context.font = "bold 12px sans-serif";
context.fillText("x", 248, 43);
context.fillText("y", 58, 165);

 ↜ Změna stylu písma

The fillText() method draws the actual text.

context.font = "bold 12px sans-serif";
context.fillText("x", 248, 43);
context.fillText("y", 58, 165);

 ⇜ Kreslení textu

Zeptejte se profesora Značky

Otázka: Můžu při kreslení na plátno používat relativní velikost fontu?
Odpověď: Ano. Samotný element <canvas>, stejně jako kterýkoliv jiný HTML element na vaší stránce, vypočítal velikost písma na základě CSS pravidel na vaší stránce. Pokud nastavíte vlastnost context.font v relativních jednotkách, například 1.5em nebo 150%, prohlížeč ji vynásobí vypočítanou velikostí písma elementu <canvas> samotného.

Řekněme, že chci aby text v levém horním rohu začínal na y=5. Jenže já jsem líný — nechci měřit výšku textu a počítat velikost účaří. Místo toho můžu nastavit textBaseline na top a nastavit souřadnice levého horního rohu ohraničení textu.

context.textBaseline = "top";
context.fillText("( 0 , 0 )", 8, 5);

Pojďme na text v pravém spodním rohu. Řekněme, že chci, aby byl pravý horní roh textu na souřadnici (492,370) — jen pár pixelů od pravého spodního rohu plátna — ale nechci měřit výšku ani šířku textu. Můžu nastavit textAlign na right a textBaseline na bottom a potom zavolat fillText() se souřadnicemi pravého spodního rohu ohraničení textu.

context.textAlign = "right";
context.textBaseline = "bottom";
context.fillText("( 500 , 375 )", 492, 370);

A toto je výsledek:

Hopla! Zapomněli jsme na tečky v rozích. Podíváme se až později, jak je nakreslit. Pro teď budu trochu podvádět a nakreslím je jako obdélníky.

context.fillRect(0, 0, 3, 3);
context.fillRect(497, 372, 3, 3);

 ⇜ Nakresli dvě „tečky“

A to je vše! Tady je finální produkt:

Přechody

IEFirefoxSafariChromeOperaiPhoneAndroid
lineární přechody7.0+*3.0+3.0+3.0+10.0+1.0+1.0+
radiální přechody9.0+3.0+3.0+3.0+10.0+1.0+1.0+
* Internet Explorer 7 and 8 vyžadují explorercanvas knihovnu třetích stran. Internet Explorer 9 podporuje přechody v elementu <canvas> nativně.

Z předchozích odstavců této kapitoly jsme se naučili, jak vytvořit obdélník s jednobarevnou výplní a jednobarevnou čáru. Jenže tvary ani čáry nemusí být pouze jednobarevné, můžete dělat spoustu kouzel s přechody. Podívejte se na příklad.

Zdrojový kód vypadá stejně, jako u kteréhokoliv jiného plátna.

<canvas id="d" width="300" height="225"></canvas>

Nejprve musíme najít element <canvas> a jeho kreslicí prostředí.

var d_canvas = document.getElementById("d");
var context = d_canvas.getContext("2d");

Jakmile máme k dispozici kreslicí prostředí, můžeme začít pracovat s přechody. Přechod chápejte jako plynulou změnu mezi dvěma nebo více barvami. Kreslicí prostředí podporuje dva typy přechodů:

  1. createLinearGradient(x0, y0, x1, y1) vykreslený podél úsečky z bodu (x0, y0) do bodu (x1, y1).
  2. createRadialGradient(x0, y0, r0, x1, y1, r1) vykreslený podél kuželu mezi dvěma kruhy. První tři parametry reprezentují začátek kruhu se středem v bodu (x0, y0) a poloměrem r0. Poslední tři parametry reprezentují konec kruhu se středem v bodu (x1, y1) a průměrem r1.

Pojďme vyzkoušet lineární přechod. Přechody mohou být jakkoliv velké; já ho udělám 300 pixelů široký, stejně jako je plátno.

Vytvoř objekt přechodu

var my_gradient = context.createLinearGradient(0, 0, 300, 0);

Protože jsou hodnoty y (druhý a čtvrtý parametr) obě nulové, rozprostře se přechod rovnoměrně zleva do prava.

Jakmile máme pro přechod připravený objekt, můžeme definovat jeho barvy. Přechod má dva nebo více barevných milníků. K přidání milníku stačí zadat jeho pozici kdekoliv podél přechodu mezi 0 a 1.

Pojďme vytvořit přechod z černé do bílé.

my_gradient.addColorStop(0, "black");
my_gradient.addColorStop(1, "white");

Definicí přechodu na plátno zatím nic nenakreslíme. Jde zatím jen o objekt zastrčený někde v paměti. Abychom přechod nakreslili, nastavíme fillStyle na přechod a nakreslíme tvar, stejně jako u obdélníků nebo čar.

Výplní bude přechod

context.fillStyle = my_gradient;
context.fillRect(0, 0, 300, 225);

A toho je výsledek:

Řekněme, že budete potřebovat přechod, který půjde shora dolů. Jakmile pro přechod vytvoříte objekt, ponechte hodnoty pro osu x (první a třetí parametr) konstantní, a nastavte hodnoty osy y (druhý a čtvrtý parametr) v rozmezí 0 až výška plátna.

Hodnoty x jsou stejné, y se liší

var my_gradient = context.createLinearGradient(0, 0, 0, 225);
my_gradient.addColorStop(0, "black");
my_gradient.addColorStop(1, "white");
context.fillStyle = my_gradient;
context.fillRect(0, 0, 300, 225);

A toto je výsledek:

Můžete klidně vytvořit i diagonální přechod.

Hodnoty x i y jsou různé

var my_gradient = context.createLinearGradient(0, 0, 300, 225);
my_gradient.addColorStop(0, "black");
my_gradient.addColorStop(1, "white");
context.fillStyle = my_gradient;
context.fillRect(0, 0, 300, 225);

A toto je výsledek:

Obrázky

IEFirefoxSafariChromeOperaiPhoneAndroid
7.0+*3.0+3.0+3.0+10.0+1.0+1.0+
* Internet Explorer 7 a 8 vyžadují explorercanvas knihovny třetích stran. Internet Explorer 9 podporuje obrázky v elementu <canvas> nativně.

Tady máme kočku:

sleeping cat

 ⇜ Element <img>

A tady máme stejnou kočku, ale nakreslenou na plátně:

Element <canvas> ⇝ 

Pro kreslení obrázků na plátno máme v kreslicím prostředí metodu drawImage(). Této metodě můžete předat tři, pět nebo devět argumentů.

Specifikace HTML5 vysvětluje parametry drawImage():

Zdrojová oblast je obdélník (uvnitř zdrojového obrázku) jehož rohy jsou čtyři body (sx, sy), (sx+sw, sy), (sx+sw, sy+sh), (sx, sy+sh).

Cílová oblast je obdélník (uvnitř plátna) jehož rohy jsou čtyři body (dx, dy), (dx+dw, dy), (dx+dw, dy+dh), (dx, dy+dh).

diagram of drawImage parameters

Abychom mohli vykreslit obrázek na plátno, potřebujeme nejprve obrázek samotný. Obrázkem může být existující <img> element nebo můžete JavaScriptem vytvořit objekt Image(). V obou případech si musíte být jistí, že se obrázek správně nahrál, než se jej pokusíte vykreslit na plátno.

Využijete-li existujícího <img> elementu, můžete jej na plátno v klidu vykreslit v průběhu události window.onload.

využití elementu <img>

<img id="cat" src="images/cat.png" alt="spící kočka" width="177" height="113">
<canvas id="e" width="177" height="113"></canvas>
<script>
window.onload = function() {
  var canvas = document.getElementById("e");
  var context = canvas.getContext("2d");
  var cat = document.getElementById("cat");
  context.drawImage(cat, 0, 0);
};
</script>

Pokud vytváříte obrázek výhradně JavaScriptem, můžete jej na plátno bezpečně vykreslit v průběhu události Image.onload.

využití objektu Image()

<canvas id="e" width="177" height="113"></canvas>
<script>
  var canvas = document.getElementById("e");
  var context = canvas.getContext("2d");
  var cat = new Image();
  cat.src = "images/cat.png";
  cat.onload = function() {
    context.drawImage(cat, 0, 0);
  };
</script>

Třetí a čtvrtý parametr metody drawImage() jsou nepovinné a měníte jimi rozměry obrázku. Toto je stejný obrázek, pouze s poloviční šířkou a výškou, opakovaně vykreslený na různých souřadnicích jednoho plátna.

Tady je skript tvořící efekt „multikočky“:

cat.onload = function() {
  for (var x = 0, y = 0;
       x < 500 && y < 375;
       x += 50, y += 37) {
    context.drawImage(cat, x, y, 88, 56);
  }
};

 ⇜ změna rozměrů obrázku

Tato snaha vyvolává legitimní otázku: proč byste měli chtít vykreslovat obrázek na plátno? V čem je extra komplexní nastavení obrázků na plátně lepší než element <img> a pár CSS pravidel? I efekt „multikočky“ je nahraditelný deseti překrývajícími se <img> elementy.

Jednoduchá odpověď je, že z nějakého důvodu možná budete chtít vykreslit text na plátno. Například diagram souřadnic plátna obsahuje text, čáry a tvary; text na plátně byla pouze součást. Složitější diagram může snadno využít drawImage() například k vykreslení ikon nebo jiných grafických prvků.

A co Internet Explorer?

Internet Explorer nepodporuje canvas API do verze 9.0. (IE9 plně podporuje canvas API.) Nicméně tyto starší verze Internet Exploreru podporují proprietární technologii zvanou VML, která dokáže skoro totéž, co <canvas> element. A proto se zrodil excanvas.js.

Explorercanvas (excanvas.js) je JavaScriptová open source knihovna, licencovaná pro Apache, která implementuje canvas API do Internet Exploreru. Abyste ji mohli využít, stačí vložit následující skript do hlavičky vaší stránky.

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Dive Into HTML5</title>
  <!--[if lt IE 9]>
    <script src="excanvas.js"></script>
<![endif]-->
</head>
<body>
  ...
</body>
</html>

Podmíněné komentáře <!--[if lt IE 9]> a <![endif]--> Internet Explorer interpretuje jako: „pokud je vaším prohlížečem Internet Explorer ve verzi nižší než 9.0, pak proveď tento kód“. Ostatní prohlížeče tuto pasáž berou jako obyčejný HTML komentář. Ve výsledku si Internet Explorer 7 a 8 stáhne a spustí excanvas.js, zatímco všechny ostatní prohlížeče budou skript ignorovat (nebudou ho stahovat ani spouštět, zkrátka nic). Stránky se tak v prohlížečích s nativní podporou canvas API načtou bez sebemenšího zpomalení.

Jakmile zahrnete excanvas.js v elementu <head> vaší stránky, nemusíte si už s Internet Explorerem dělat starost. Zkrátka v klidu zahrňte element <canvas> do vašeho kódu. Dodržte instrukce pro práci s kreslicím prostředím elementu <canvas>, popsané výše v této kapitole, a můžete kreslit tvary, texty a vzorky dle libosti.

No dobře… ne tak úplně. Je tam pár omezení:

  1. Přechody mohou být pouze lineární. Kruhové přechody nejsou podporovány.
  2. Vzorek se musí opakovat v obou směrech.
  3. Oblasti ořezu nejsou podporovány.
  4. Nejednotné škálování mění nesprávně rozměry okrajů.
  5. Je to pomalé. To asi není velké překvapení vzhledem k tomu, že JavaScriptový parser Internet Exploreru zkrátka je pomalejší, než tomu je u ostatních prohlížečů. Začnete-li kreslit složitější tvary skrz JavaScriptovou knihovnu, která překládá příkazy naprosto rozdílné technologii, nedivte se „kde to vázne“. Když nakreslíte pár čar nebo transformujete obrázek, nezaznamenáte žádný zásadní pokles výkonu. Určitě si ale všimnete, jakmile zkusíte pracovat s animacemi nebo dalšími skopičinami, které element <canvas> nabízí.

Při používání excanvas.js je ještě jeden zádrhel, na který jsem narazil při vytváření příkladů v této kapitole. ExplorerCanvas automaticky inicializuje prostředí vlastního rádoby-plátna kdykoliv vložíte excanvas.js skript do vaší HTML stránky. Jenže to neznamená, že ho Internet Explorer zvládne okamžitě používat. V některých případech můžete narazit na situaci, kdy je rádoby-plátno skoro, ale ještě ne úplně, připravené. Hlavním příznakem tohoto stavu je stížnost „objekt nepodporuje tuto vlastnost nebo metodu“, kterou Internet Explorer vrátí kdykoliv zkusíte jakkoliv pracovats elementem <canvas>, potažmo kreslicím prostředím.

Nejjednodušší řešení je odložit všechny akce související s plátnem, dokud „nezahouká“událost onload. Možná to chvilku potrvá — pokud mají vaše stránky hodně obrázků nebo videa, jejich zpracování oddálí událost onload — ale to dá ExplorerCanvasu potřebný čas na jeho kouzla.

Kompletní, živý příklad

Halma je stovky let stará desková hra a vznikla spousta jejích variací. V tomto příkladu jsem vytvořil solitérní verzi Halmy s devíti figurkami na hracím plánu s 9 × 9 poli. Na začátku hry figurky vytváři čtverec 3 × 3 v levém spodním rohu hrací desky. Cílem hry je přesunout všechny figurky v co nejméně tazích tak, aby zformovaly čtverec 3 × 3 v pravém horním rohu.

Ve hře Halma jsou dva typy povolených tahů:

Tak takhle se Halma hraje. Můžete si ji zahrát na samostatné stránce pokud byste se v ní chtěli šťourat se svými vývojářskými udělátky.

Počet tahů: 0

Jak to funguje? Jsem rád, že se ptáte. Nebudu tu ukazovat celý kód. (Můžete se na něj podívat na diveintohtml5.org/examples/halma.js.) Vlastně přeskočím většinu hracího kódu, ale rád bych vypíchnul pár částí kódu, které obsluhují kliknutí myší v oblasti plátna a samotné „kreslení na plátno“.

Zatímco se stránka načítá, inicializujeme hru nastavením rozměru elementu <canvas> a uchováme referenci k jeho kreslicímu prostředí.

gCanvasElement.width = kPixelWidth;
gCanvasElement.height = kPixelHeight;
gDrawingContext = gCanvasElement.getContext("2d");

Pak uděláme něco, co jste dosud neviděli: navěsíme na element <canvas> odchytávání událostí, abychom mohli zpracovávat kliknutí myší.

gCanvasElement.addEventListener("click", halmaOnClick, false);

Funkce halmaOnClick() se zavolá kdykoliv uživatel klikne někde uvnitř plátna. Jejím argumentem je objekt MouseEvent, který obsahuje informaci kam uživatel kliknul.

function halmaOnClick(e) {
    var cell = getCursorPosition(e);

    // the rest of this is just gameplay logic
    for (var i = 0; i < gNumPieces; i++) {
	if ((gPieces[i].row == cell.row) && 
	    (gPieces[i].column == cell.column)) {
	    clickOnPiece(i);
	    return;
	}
    }
    clickOnEmptyCell(cell);
}

V dalším kroku díky objektu MouseEvent zjistíme, na které pole hrací desky uživatel právě kliknul. Hrací deska Halmy zabere plochu celého plátna, takže každé kliknutí směřuje na některé z polí. Jen musíme zjistit na které. To je ošidné, protože každý prohlížeč zpracovává události myši rozdílně.

function getCursorPosition(e) {
    var x;
    var y;
    if (e.pageX != undefined && e.pageY != undefined) {
	x = e.pageX;
	y = e.pageY;
    }
    else {
	x = e.clientX + document.body.scrollLeft +
            document.documentElement.scrollLeft;
	y = e.clientY + document.body.scrollTop +
            document.documentElement.scrollTop;
    }

V tuto chvíli máme souřadnice x a y, relativní vůči dokumentu (což je celá HTML stránka). To je nám zatím celkem k ničemu, protože potřebujeme souřadnice relativní vůči plátnu.

    x -= gCanvasElement.offsetLeft;
    y -= gCanvasElement.offsetTop;

Nyní máme souřadnice x a y, které jsou relativní vůči plátnu. To znamená, že pokud se x i y rovná nule, víme, že uživatel kliknul na pixel nejbližší levému hornímu rohu plátna.

Díky tomu jsme schopni spočítat na které pole uživatel kliknul a podle toho se zachovat.

    var cell = new Cell(Math.floor(y/kPieceHeight),
                        Math.floor(x/kPieceWidth));
    return cell;
}

Páni! Události myši nejsou snadné. Ve svých aplikacích, založených na elementu <canvas>, můžete využít stejnou logiku (vlastně dokonce i přímo tento kód). Pamatujte: kliknutí → souřadnice relativní k dokumentu → souřadnice relativní k elementu <canvas> → specifický kód aplikace.

Fajn, pojďme se podívat na základní kreslicí rutiny. Protože je grafika hry velmi jednoduchá, rozhodl jsem se vymazat a překreslit celou hrací desku kdykoliv ve hře dojde k nějaké změně. Toto není nezbytně nutné. Kreslicí prostředí plátna zachová vše, co jste na ně předtím nakreslili. Dokonce i když uživatel odroluje plátno mimo viewport prohlížeče nebo klikne-li na jiný tab a později se vrátí zpět. Pokud pomocí elementu <canvas> vytváříte aplikaci se složitější grafikou (jako například hry), můžete optimalizovat výkon sledováním oblastí, které jsou na plátně „špinavé“ a jen ty překreslovat. Ale to už je trochu mimo záběr této knihy.

gDrawingContext.clearRect(0, 0, kPixelWidth, kPixelHeight);

Postup při kreslení hrací desky vypadá povědomě. Ostatně, je podobný kreslení diagramu souřadnic plátna popsaném dříve v této kapitole.

gDrawingContext.beginPath();

/* vertical lines */
for (var x = 0; x <= kPixelWidth; x += kPieceWidth) {
    gDrawingContext.moveTo(0.5 + x, 0);
    gDrawingContext.lineTo(0.5 + x, kPixelHeight);
}

/* horizontal lines */
for (var y = 0; y <= kPixelHeight; y += kPieceHeight) {
    gDrawingContext.moveTo(0, 0.5 + y);
    gDrawingContext.lineTo(kPixelWidth, 0.5 +  y);
}

/* draw it! */
gDrawingContext.strokeStyle = "#ccc";
gDrawingContext.stroke();

Skutečná zábava začíná, jakmile začneme kreslit každou z figurek. Figurka je kruh, tedy něco, co jsme zatím nekreslili. Mimoto, pokud uživatel zvolí figurku s úmyslem přesunu na jiné pole, chceme ji vybarvit.

Argument p reprezentuje figurku, která má vlasnost row (řada) a column (sloupec), které popisují současnou polohu figurky na hrací desce. Využijeme herních konstant k překladu (column, row) na souřadnice (x, y) relativní k plátnu, poté nakreslíme kruh, který (je-li figurka vybraná) vyplníme barvou.

function drawPiece(p, selected) {
    var column = p.column;
    var row = p.row;
    var x = (column * kPieceWidth) + (kPieceWidth/2);
    var y = (row * kPieceHeight) + (kPieceHeight/2);
    var radius = (kPieceWidth/2) - (kPieceWidth/10);

To je všechno z logiky specifické pro tuto hru. Nyní máme souřadníce (x, y), relativní vůči plátnu, pro střed kruhu, který chceme kreslit. V canvas API metoda circle() není, ale můžeme využít metodu arc() (česky „oblouk“). Vždyť přeci, co jiného je kruh, než oblouk kolem dokola? Vzpomínáte na základy geometrie? Metoda arc() potřebuje středový bod (x, y), poloměr, počáteční a konečný úhel (v radiánech) a příznak směru (false znamená „po směru hodinových ručiček“, true naopak). Pro výpočet radiánů můžeme využít JavaScriptového modulu Math.

gDrawingContext.beginPath();
gDrawingContext.arc(x, y, radius, 0, Math.PI * 2, false);
gDrawingContext.closePath();

Počkat! Zatím se nic nevykreslilo. Stejně jako metody moveTo() a lineTo, i arc() nejprve „kreslí tužkou“. Abychom nakreslili kruh, potřebujeme ještě nastavit strokeStyle a zavolat stroke(), který skicu kruhu „obtáhne inkoustem“.

gDrawingContext.strokeStyle = "#000";
gDrawingContext.stroke();

A co když je figurka vybraná? Můžeme použít stejnou cestu, kterou jsme už vytvořili pro obrys figurky, a tu vyplnit barvou.

if (selected) {
    gDrawingContext.fillStyle = "#000";
    gDrawingContext.fill();
}

A to je… no, skoro všechno. Zbytek programu je logika specifická pro tuto hru — rozhodování mezi platnými a nepovolenými tahy, sledování počtu tahů, detekce ukončení hry. S devití kruhy, pár čarami a jednou onclick událostí jsme vytvořili celou hru v elementu <canvas>. Huzzah!

Další čtení

Dočetli jste kapitolu “Říkejme tomu (plocha na) kreslení.” Pokud chcete pokračovat, přejděte na celý obsah knihy.

Víte že?

O’Reilly spolu s Google Press nabízí anglickou verzi této knihy v řadě formátů včetně tištěné pdoby, ePub, Mobi, and DRM-free PDF. Placená verze se nazývá „HTML5: Up & Running,“ a můžete ji mít ihned.

Pokud se vám tahle kapitola líbila a chcete autora anglického originálu podpořit, kupte si „HTML5: Up & Running“ skrze tento affiliate odkaz nebo elektronickou verzi přímo od O’Reilly. Vy dostanete knihu, já peníze. V současnosti nepřijímám přímé dary.

Copyright MMIX–MMXI Mark Pilgrim, Czech translation Jiří Sekera