Přejít k navigační liště

Zdroják » Webdesign » Canvas – říkejme tomu plocha na kreslení

Canvas – říkejme tomu plocha na kreslení

Články Webdesign

Podívejme se na canvas z HTML5. Předvedeme si kreslení základních tvarů, cest, barevných přechodů a používání obrázků. Nakonec zkusíme v canvasu naprogramovat hru Halma.

Tento článek je zkráceným překladem kapitoly Let’s Call It A Draw(ing Surface) knihy Marka Pilgrima Dive into HTML5, jejíž komunitní překlad probíhá na HTML5.cz a je šířena pod licencí CC-BY-3.0.

HTML5 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>
IE Firefox Safari Chrome Opera iPhone Android
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.

Základní kód vypadá přibližně takhle:

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

Na jedné stránce můžete mít více elementů <canvas>. Každý canvas se zobrazí v DOMu a má svůj vyhrazený prostor. 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

IE Firefox Safari Chrome Opera iPhone Android
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, třeba černý obdélník:

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.

Co ovšem znamená tohle?

  var b_context = b_canvas.getContext("2d");

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čů experimentovali s vlastními 3D canvas API, 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:

  • Vlastnost fillStyle (styl výplně) může být CSS barva, vzorek nebo přechod. (Více o přechodech později.) Implicitně je fillStyle nastavena jako černá, ale můžete ji nastavit jakkoliv chcete. Každé kreslicí prostředí si pamatuje své vlastnosti, dokud stránku nezavřete nebo je sami nezresetujete.
  • fillRect(x, y, šířka, výška) nakreslí obdélník s aktuálním stylem výplně.
  • Vlastnost strokeStyle (styl orámování) je podobná vlastnosti fillStyle  — může být CSS barvou, vzorkem nebo přechodem.
  • strokeRect(x, y, šířka, výška) nakreslí obdélník s aktuálním stylem orámování. Vlastnost strokeRect nevyplňuje střed, nakreslí pouze okraje.
  • clearRect(x, y, šířka, výška) odstraní pixely v definovaném obdélníku.

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…

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.

Jak tento diagram souřadnic nakreslit pomocí elementu <canvas>? Skládá se ze:

  • soustavy světle šedých vertikálních čar,
  • soustavy světle šedých horizontálních čar,
  • dvou černých horizontálních čar,
  • dvou malých diagonál, které vytváří šipku,
  • dvou černých vertikálních čar,
  • dvou malých diagonálních čar, které vytváří druhou šipku,
  • písmene „x“,
  • písmene „y“,
  • textu „(0, 0)“ poblíž levého horního rohu,
  • textu „(500, 375)“ u pravého spodního rohu,
  • tečky v pravém horním rohu a druhé v levém spodním rohu.

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

IE Firefox Safari Chrome Opera iPhone Android
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ř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í je na plátno. Barvu čar nastavuje strokeStyle. Toto je výsledek:

Otázka: Proč jsi začal souřadnice xy 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:

A line from (1,0) to (1,3) is drawn 2 pixels wide

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:

A line from (1.5,0) to (1.5,3) is draw 1 pixel wide.

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.

context.beginPath(); // Nová cesta
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);

Ří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:

IE Firefox Safari Chrome Opera iPhone Android
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. Na rozdí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:

  • font může být cokoliv, co byste vložili do pravidla fontCSS: font style, font variant, font weight, font size, line height a font family.
  • textAlign ovlivňuje zarovnání textu. Je velmi podobný (ale ne identický) CSS pravidlu text-align. Možné hodnoty jsou start, end, left, right a center.
  • textBaseline určuje kde je text vykreslen, relativně k startovnímu bodu. Možné hodnoty jsou top, hanging, middle, alphabetic, ideographic nebo bottom.

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ří (baseline), viz obr.

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";// Změna stylu písma
context.fillText("x", 248, 43); // Kreslení textu
context.fillText("y", 58, 165);

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);

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

Přechody

IE Firefox Safari Chrome Opera iPhone Android
lineární přechody 7.0+* 3.0+ 3.0+ 3.0+ 10.0+ 1.0+ 1.0+
radiální přechody 9.0+ 3.0+ 3.0+ 3.0+ 10.0+ 1.0+ 1.0+
* Internet Explorer 7 and 8 vyžadují explorercanvas knihovny 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.

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 doprava.

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 toto 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.

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.

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

IE Firefox Safari Chrome Opera iPhone Android
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ě.

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ů.

  • drawImage(image, dx, dy) vezme obrázek a vykreslí ho na plátno. Levý horní roh obrázku bude na souřadnicích (dx, dy). Pokud tedy budete chtít vykreslit obrázek v levém horním rohu plátna, jednoduše zadáte souřadnice (0, 0).
  • drawImage(image, dx, dy, dw, dh) vezme obrázek a vykreslí ho ze souřadnic (dx, dy) v takovém měřítku, aby odpovídal šířce dw a výšce dh.
  • drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) vezme obrázek, vyřízne z něj oblast (sx, sy, sw, sh), změní jeho rozměry na (dw, dh) a vykreslí ho na plátno ze souřadnic (dx, dy).

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).

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.

<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.

<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. Tady je obrázek kočky:

A 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 pracovat s 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ů:

  • Přesunete figurku do kteréhokoliv volného přilehlého pole. „Volným“ polem rozumíme kterékoliv pole, na kterém není jiná figurka. „Přilehlá“ pole je sousední pole na sever, jih, východ, západ, severovýchod, severozápad, jihovýchod nebo jihozápad hned vedle pole, ve kterém se figurka právě nachází. (Hrací plán nepokračuje za své okraje. Pokud je figurka v poli na levém okraji plánu, nemůže být přesunuta na západ, severozápad nebo jihozápad. Pokud je figurka v poli u spodního okraje, nemůže být přesunuta na jih, jihovýchod nebo jihozápad.)
  • Přeskočíte figurku na přilehlém poli, a to i opakovaně. To znamená, že když přeskočíte přes figurku na přilehlém poli a hned přeskočíte další figurku na poli sousedícím k vaší nové pozici, jde pouze o jeden tah. Ve skutečnosti může být považován za jeden tah jakýkoliv počet těchto přeskočení. (Vzhledem k tomu, že je cílem minimalizovat celkový počet tahů, snažíme se rozmístit figurky tak, abychom ostatními figurkami mohli provést co nejdelší sekvence skoků v co nejméně tazích).

Tak takhle se Halma hraje. Můžete si ji zahrát na samostatné stránce.

Jak to funguje? Jsem rád, že se ptáte. Nebudu tu ukazovat celý kód. (Můžete se na něj podívat sami.) 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);

    // pak ještě zbývá herní logika
    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řece 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 devíti kruhy, pár čarami a jednou onclick událostí jsme vytvořili celou hru v elementu <canvas>. Huzzah!

Další čtení

Zbytek právě probíhajícího komunitního překladu knihy Marka Pilgrima Dive into HTML5 najdete na HTML5.cz.

Komentáře

Subscribe
Upozornit na
guest
19 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
sitnarf

API je jistě použitelné, bohužel, na programování aplikací pro širokou veřejnost na webu nepoužitelné, statistiky mluví jasně.

bauglir

tak společně s explorercanvas je podpora slušná bych řekl…

ale obecně na aplikace? někteří uživatelé nepřejdou „nikdy“, někteří přejdou, pokud se jim dá správný důvod… jako například slušná aplikace… :)

Martin Hassman

Tak statistiky mluví opravdu jasně a už pár let dost pozitivně, s excanvasem canvas funguje klidně i v IE6 (detaily jsou popsány v článku). Pro statická použití (neanimovanou grafiku nebo jen minimálně animovanou) se canvas používá už pár let bez problému. Teď pomalu přichází doba i pro ta dynamičtější řešení.

Petr

Je výborně čtivej!

Jan33

Mě to zase vůbec nevyhovovalo.

Koca

Canvas vyuzivam pro aplikaci na mereni vykonu solarnich elektraren, kde je v grafu treba 150 krivek (pro kazdy menic zvlast) a musim rict, ze to s vykonem neni zadna slava. S excanvas vykresleni tohoto grafu trva neskutecne dlouho.

Mnohem lepsi se jevi pouziti SVG, ktere podporuje uz IE6 nativne. Napr. s pomoci knihovnou raphael (http://raphaeljs.com/). SVG toho umi vic, na svg elementy se daji bindovat eventy stejne jako na html elementy.

Obrovskou vyhodou je to, ze to je vektorova grafika. Takze kdyz to v prohlizeci (hlavne treba na telefonu ci tabletu) priblizite, grafika se vam zaostri, u canvasu nikoliv.

Mozna by to mohl byt navrh na dalsi clanek – srovnani svg a canvasu. Muj nazor je, ze svg je lepsi.

Martin Hassman

O rapaheljs jsme se už dřív zmiňovali, stačí mrknout do archivu.

A SVG, že je lepší než canvas? To bude ovšem nějaký nesmysl 8-)

Vážně, u SVG x canvasu se nezdá, že by jedna z těch technologií měla jednou pro vždy předhonit a nahradit tu druhou. Je tu místo pro obě dvě (ostatně vektorová a bitmapová grafika obecně tu jsou obě už pár let vedle sebe a nic se na tom nemění). Základem je se naučit, kdy po které z nich sáhnout. Doporučuji googlovat ‚canvas vs svg‘, už se na tohle téma objevila řada úvah.

František Kučera

Jistě, nejde říct, zda je obecně lepší vektorová nebo bitmapová grafika. Záleží na tom, jaké máme podklady, vstupy – zda to a) jsou fotografie nebo potřebujeme rastr a do něj chceme vykreslovat konkrétní body nebo b) máme nějaké objekty a ty chceme vykreslit. Většinou to budou spíš ty objekty*. Další možnost je mít plátno/rastr a nad ním si napsat nějakou abstraktní vrstvu, která umožní ty objekty vykreslovat, ale to je takové znovu-vynalézání kola, když můžeme použít již hotovou abstraktní vrstvu (SVG knihovnu v prohlížeči). Je to asi jako vykreslovat si 3D grafiku bod po bodu a scénu si počítat sám v aplikaci/hře, což je hodně neefektivní vs. použít OpenGL se všemi výhodami (méně kódu, znovupoužitelnost, hardwarová akcelerace…).

Zrovna jak tu jsou někde výše/níže ty miny – tam by se daleko víc hodilo SVG.

*) ve kterých můžou být i vložené bitmapy, takže člověk může mít oboje.

Tisnovsky

Jasne, Canvas a SVG predstavuji dva odlisne pristupy k tvorbe grafiky, ktere se navzajem doplnuji, ale nelze obecne rict, ze jeden pristup je vzdy lepsi. Obecne se rozlisuje mezi rezimem retainded a immediate (hodne se to pouziva v 3D grafice, ale plati i pro 2D).

Canvas nabizi primy pristup k 2D pixmape, SVG zase graf sceny. I kdyby nakrasne obe technologie byly 100% podporovany ve vsech vyznamnych prohlizecich (dockame se? :-), tak to neznamena, ze jedna vytlaci druhou, spis se budou objevovat knihovny, ktere vytvori retained mode nad Canvasem (resp. ty knihovny uz tady jsou).

pas

Nebo se to taky da rict tak, ze vztah mezi canvasem a SVG je takovy, ze v canvasu je mozne napsat renderer SVG (napr. uz existuje i renderer flashovych vektorovych animaci), zatimco obracene to dost dobre nejde. :)

Opi

Delat hry v HTML5 Canvas je zabava. Ale nikdy se tolik neprosadi, jaky to ve Flashi. Jednim z hlavnich duvodu je, ze jsou volne pristupne zdrojove kody…

Ukazka z moji tvorby:
http://jan.danihelka.net/javascript/mars/mars.html

http://jan.danihelka.net/javascript/mines/mines.html

Martin Hassman

To je pěkné, pošlu to na náš Twitter, díky za odkazy.

František Kučera

Ad „Jednim z hlavnich duvodu je, ze jsou volne pristupne zdrojove kody…“

A to je problém? Mně to přijde spíš jako výhoda.

Ad „Ukazka z moji tvorby“

Pěkné, chvilku jsem si zahrál :-) Ale na druhou stranu musím říct, že na to, jak je to jednoduchá hra/grafika, je to poměrně dost líné, zrovna u takové skákačky je přesnost a odezva dost důležitá Myslím, že i na 286 byly takové hry svižnější…

pas

Mezi obfuskovaným / minifikovaným JavaScriptem a ActionScriptovým bytekódem snad zas tak velký rozdíl není…

Evelyn

Ehm… můžu být zlá?
„Vždyť přece co jiného je kruh, než oblouk kolem dokola? Vzpomínáte na základy geometrie?“

Jo, vzpomínám. A oblouk kolem dokola je <b>kružnice</b>. Kruh rovná se plošný útvar. Paní učitelka Krejčová (tehdy bohužel ještě soudružka) mi to, dej jí Pámbu věčnou slávu, vtloukla do hlavy dokonale.

Ale to už hledám hnidy, nenechte se rozptylovat. :-)

Martin Hassman

Ano, to bylo ztraceno v překladu, circle zde je opravdu kružnice.
A být zlý primárně nevyžaduje povolení, ale schopnost přijmout následky 8-)

Člověk

Pouze bych doplnil, že 3d canvas existuje a je zaobalen do nadcházejícího „balíčku“ html 5 standardů – můžete ho nalézt pod termínem WebGL. Dostupný je ve všech nových verzí prohlížečů nativně včetně exploreru.

pas

V IE přece není.

Martin Hassman

V IE opravdu není. Existuje jakýsi plugin http://iewebgl.com/ , ale ten situaci na trhu nevyřeší.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.

Pocta C64

Za prvopočátek své programátorské kariéry vděčím počítači Commodore 64. Tehdy jsem genialitu návrhu nemohl docenit. Dnes dokážu lehce nahlédnout pod pokličku. Chtěl bych se o to s vámi podělit a vzdát mu hold.