Lekce 12 - Obrázky a kreslení na canvas v JavaScriptu

JavaScript Základní konstrukce Obrázky a kreslení na canvas v JavaScriptu American English version English version

ONEbit hosting Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem. Vydávání, hosting a aktualizace umožňují jeho sponzoři.

Vítám vás u další lekce o tvorbě webových aplikací v JavaScriptu. V lekci minulé, Dokončení editoru tabulek v JavaScriptu, jsme dokončili editor tabulek. V tomto JavaScript tutoriálu se podíváme na zub obrázkům.

Na webu existují dva typy obrázků – statické a dynamické. Statické obrázky již znáte a moc se s nimi nedá kouzlit. Jsou to obyčejné obrázky (tag <img>), se kterými můžete provádět jen několik málo základních operací. Povětšinou s nimi manipulujeme jen jako s prvkem DOMu. Dynamické obrázky již mohou být v dokumentu HTML znázorněny 2 značkami - <svg> a <canvas>. Přičemž první jmenovaná sice není striktně statická, ale vesměs v JavaScriptu s ní toho moc nenaděláme (zatím). Tag <canvas> již je o trochu zajímavější a zároveň jediný, jehož obsah nelze definovat jinak než JavaScriptem. Projděme si vše postupně.

Statické obrázky

Jak jsem již zmínil v úvodu, jedná se o klasický obrázek, který v HTML definujeme tagem <img>. Můžeme mu samozřejmě obsluhovat různé události jako je kliknutí a to úplně stejným způsobem jako např. u tlačítka. V JavaScriptu ho můžeme vytvořit buďto klasicky jako prvek DOM:

let obrazek = document.createElement("img");

Nebo můžeme vytvořit nový objekt Image, což je zkrácená varianta tvorby prvku DOM:

let obrazek = new Image();

Načtení obrázku

Velkým problémem je načítání obrázku (ostatně jako načítání čehokoliv v JavaScriptu). Než s obrázkem začneme pracovat, musí se načíst. Pokud máme obrázek v HTML a JavaScript spouštíme v obsluze události onload, není žádný problém. Prohlížeč zajistí, že obsluha se zavolá až po načtení všeho (i obrázku).

V případě, že si obrázky vytváříme až v JavaScriptu, musíme na načtení počkat. Poslouží nám k tomu událost onload na daném obrázku.

let obrazek = new Image();
obrazek.src = "cesta/k/obrazku.jpg";

obrazek.onload = function () {
        // Zde víme, že je obrázek načtený
}

Příklad: Přepínač

Vytvoříme si přepínač. Jistě znáte přepínač ON-OFF. Obrázky si stáhněte níže a ideálně pojmenujte prepinac0.png a prepinac1.png.

Vytvořme stránku, kde bude obrázek s id #prepinac a jako výchozí cestu mu dáme k obrázku prepinac0.png.

<img src="prepinac0.png" id="prepinac" alt="Přepínač" />

Vytvoříme si skript, kde deklarujeme proměnnou prepinac a po načtení stránky do ní dosadíme náš element obrázku přepínače. Dále si vytvoříme funkci prepni() a nastavíme ji jako obsluhu události pro kliknutí na obrázek.

let prepinac;

function prepni() {
}

window.onload = function() {
        prepinac = document.getElementById("prepinac");
        prepinac.onclick = prepni;
}

Img.src vs. img.getAttribu­te("src")

Teď již je to jen o podmínce a změně src, ale... Objekt obrázku (u OOP zjistíte, že se jmenuje HTMLImageElement) má vlastnost src. Nabízí se tedy možnost změnit buď hodnotu HTML atributu src elementu <img> nebo ji dosadit rovnou do vlastnosti src, kterou element v JavaScriptu má. Jenže... Tyto dvě možnosti se v některých prohlížečích chovají trochu odlišně. Zatímco metoda getAttribute() vrátí přesně tu hodnotu, co je v atributu, vlastnost src vrátí absolutní cestu. Pokud tedy budeme porovnávat, tak až na pár výjimek budeme porovnávat s hodnotou v atributu. Na vyzkoušení si zdroj obrázku změníme přes vlastnost src.

function prepni() {
        if (prepinac.getAttribute("src") == "prepinac0.png") {
                prepinac.src = "prepinac1.png";
        } else {
                prepinac.src = "prepinac0.png";
        }
}

Stránku si otevřete a vyzkoušejte, přepínač by měl fungovat.

Your page
localhost

Všimněte si, že vůbec nemusíme ošetřovat, kdy se obrázek načte, protože nás to nezajímá. Uživatel bude čekat tak či tak, ale my jsme od toho odstínění, pokud s obrázkem dále nepracujeme.

Nyní se přesuneme k dynamickým obrázkům.

Dynamické obrázky

Dynamické obrázky se kreslí na plátno, které je reprezentováno elementem <canvas>. Tento element musí mít nastavené atributy width a height. Pokud je neuvedeme nebo je nastavíme např. v CSS, některé prohlížeče to nepochopí a plátno rozmažou. Na samotný element <canvas> však ještě kreslit nemůžeme, musíme si získat jeho kontext. Možná si říkáte k čemu je to dobré, získávat ještě kontext? Ono je totiž rozdíl, když na plátno kreslíme 3D hru a 2D stromeček. 3D se v současnosti teprve prosazuje jako standard WebGL a tím se teď zabývat nebudeme, nás bude zajímat jen kontext 2D.

Získání kontextu plátna

Kontext plátna získáme voláním metody getContext() a jako parametr ji předáme formou textového řetězce typ požadovaného kontextu. V našem případě je to "2d".

let platno = document.getElementById("platno");
let kontext = platno.getContext("2d");

Na tomto kontextu již můžeme volat různé metody pro vykreslení na plátno.

Obdélníky

Základním objektem je obdélník. Máme předdefinované dvě základní metody, kterými můžeme obdélník nakreslit. Jsou jimi fillRect()strokeRect(). Obě mají stejné argumenty a to x, y, výška a šířka. Funkce fillRect() obdélník vyplní, strokeRect() vykreslí pouze jeho obrys.

HTML část
<canvas width="320" height="200" id="platno"></canvas>
JS část
let platno = document.getElementById("platno");
let kontext = platno.getContext("2d");
kontext.fillRect(50, 50, 100, 100);
kontext.strokeRect(200, 50, 100, 100);

Výsledek:

Plátno
localhost

Čáry

Čáry se na plátno kreslí pomocí tzv. cest. Tyto cesty musíme (resp. měli bychom) je začít a uzavřít. Pro započetí cesty použijeme metodu beginPath(), pro uzavření pak closePath(). Samotnou čáru vykreslíme metodou lineTo(x, y). Čára se nakreslí od poslední nastavené pozice kurzoru (ta je na začátku [0;0]). Tuto pozici můžeme nastavit metodou moveTo(x, y). Metoda nevykreslí nic, pouze přesune kurzor plátna na danou pozici. Cestu vykreslíme buďto metodou fill(), která vyplní vnitřek cesty barvou (takto se dají kreslit vlastní tvary, proto je třeba cesty uzavírat), nebo metodou stroke(), která vykreslí pouze obrys dané cesty. Jednoduchý příklad níže vykreslí čáru z bodu [20;20] do [40;150].

kontext.beginPath();
kontext.moveTo(20, 20);
kontext.lineTo(40, 150);
kontext.closePath();
kontext.stroke();

Výsledek:

Plátno
localhost

Kruhy, kružnice a výseče

Další, co můžeme na plátno kreslit, jsou kruhy, kružnice a jejich výseče. To vše umí metoda arc(), jejíž syntaxe je následující:

context.arc(x, y, polomer, pocatecniUhel, konecnyUhel, smer);

Argumenty x a y určují opět absolutní pozici, zde středu. Argument polomer udává poloměr kružnice, pocatecniUhel je úhel, od kterého se má kružnice resp. výseč vykreslit. Je udán v radiánech. Z matematiky bychom si měli pamatovat, že plný úhel 2 * PI a že stupně se převedou na radiány jako (PI / 180) * stupně. Poslední argument je smer. Jedná se o logickou hodnotu (true/false), která udává, jestli se bude kružnice vykreslovat ve směru hodinových ručiček nebo proti němu. Základně je nastaven na true, tedy ve směru hodinových ručiček. Metodu musíme používat uvnitř cesty.

Kruh tedy nakreslíme takto:

kontext.beginPath();
kontext.arc(100, 100, 80, 0, Math.PI * 2);
kontext.fill();

Odměnou za kód výše nám bude takovýto kruh.

Výsledek v prohlížeči:

Plátno
localhost

Styly

Aby naše kresby dobře vypadaly, naučíme se používat styly. Rozlišujeme styly pro vyplnění (fill) a vykreslení obrysu (stroke). Styly lze aplikovat na všechny objekty od obdélníků po kruhy. Máme k dispozici dvě základní vlastnosti, se kterými dále pracují metody stroke() a fill(). Jednak je to fillStyle a pak také strokeStyle. Hodnoty těchto vlastností jsou zápisy barev. Můžeme použít klasický hexadecimální zápis z CSS, např. #ffffff nebo rgb(255, 255, 255). Lze použít také rgba(255, 255, 255, 0.5), kde poslední hodnota je tzv. alfa kanál (průhlednost), nebo hsl a hsla (stejně jako v CSS 3). A poslední možností je uvést název, pokud jej barva má (např. "green").

Ukažme si jednoduché stylování objektů:

// Styly musí být vždy před samotným vykreslením (zavoláním metody `fill()`, `stroke()` nebo samovykreslovacích metod, jako jsou `fillRect()`, `strokeRect()`...)

kontext.fillStyle = "#a8c101";
kontext.fillRect(10, 10, 50, 50);

Výsledek:

Plátno
localhost

Externí obrázky

Na plátno samozřejmě můžeme vykreslovat i existující obrázky, např. ze souborů. Musíme je však mít načtené, jinak se nevykreslí. Vykreslení obrázku docílíme jednoduše metodou drawImage(), které jako první parametr předáme obrázek a jako druhý a třetí parametr pozice X a Y kam se obrázek vykreslí. Existují ještě metody s parametry pro zvětšení/zmenšení a ořezání obrázků.

Obrázek:

Vykreslení obrázku na canvas v JavaScriptu

Výsledek:

Plátno
localhost

Text

Kromě všelijakých tvarů můžeme na plátno vykreslit i text. To se dá využít třeba jako watermark na obrázcích ve vaší galerii, popisky ke grafům nebo úplně jinak, jak vám to jen vaše fantazie dovolí. Základní funkce je fillText(text, x, y). Text zde zastupuje textový řetězec, který chceme vypsat. Argumenty x a y jsou absolutní pozice. Jednoduchý text vykreslíme třeba takto:

kontext.fillText("itnetwork.cz", 50, 50);

Text ale bude bez stylů a celkem malý. Proto máme k dispozici vlastnost context.font, kterou musíme jako třeba fillStyle nastavit ještě před vykreslením textu. Hodnoty proměnné jsou totožné se zápisem v CSS u stejnojmenné vlastnosti font. Nastavme velikost textu např. na 30 pixelů a použijme font sans-serif.

kontext.font = "30px sans-serif";
kontext.fillText("ITnetwork.cz", 50, 50);

Výsledek:

Plátno
localhost

To by na úvod do canvasu stačilo. V příští lekci, 2D kontext plátna v JavaScriptu, se podíváme na plátno ještě podrobněji.


 

Stáhnout

Staženo 475x (110.44 kB)
Aplikace je včetně zdrojových kódů v jazyce JavaScript

 

 

Článek pro vás napsal Michal Žůrek - misaz
Avatar
Jak se ti líbí článek?
18 hlasů
Autor se věnuje tvorbě aplikací pro počítače, mobilní telefony, mikroprocesory a tvorbě webových stránek a webových aplikací. Nejraději programuje ve Visual Basicu a TypeScript. Ovládá HTML, CSS, JavaScript, TypeScript, C# a Visual Basic.
Miniatura
Všechny články v sekci
Základní konstrukce jazyka JavaScript
Miniatura
Následující článek
2D kontext plátna v JavaScriptu
Aktivity (11)

 

 

Komentáře
Zobrazit starší komentáře (11)

Avatar
Michal Žůrek - misaz:6.7.2017 17:27

V javascriptu se jako oddělovač složek nepoužívá \ ale /

Odpovědět 6.7.2017 17:27
Nesnáším {}, proto se jim vyhýbám.
Avatar
Jan Felkl
Člen
Avatar
Jan Felkl:6.7.2017 17:30

dik už to jede

 
Odpovědět 6.7.2017 17:30
Avatar
David Koníček:18.12.2017 8:19

Škoda, že tu není uvedený příklad jak vykreslit ten externí obrázek.. až po delším bádání jsem přišel jak na to. Klidně bych se u toho trochu víc rozepsal, ale nechci kritizovat.

Odpovědět 18.12.2017 8:19
Věř, běž a dokážeš!
Avatar
Tomáš Pařízek:7. února 12:26

Mám takové dva dotazy, nevím přesně kde se mám zeptat, tak se ptám u tohoto článku.

1.) Proč místo označení proměnné var používáte let?
2.) Co znamená this, případně mohl byste někdo uvést jednoduchý příklad?

 
Odpovědět 7. února 12:26
Avatar
Odpovídá na Tomáš Pařízek
Michal Žůrek - misaz:7. února 14:06
  1. let jsem tam nenapsal já, ale někdo to upravil. Je to dnes hrozně cool. Oproti var se to chová jinak napříč různými scope a nedovoluje to za určitých okolností změnu hodnoty proměnné. Nevýhoda je, že to neumí starší prohlížeče. Osobně to nepoužívám.
  2. this slouží k určení kontextu volání. Používá se u objektů a metod k přístup k aktuální instanci.
function Clovek(jmeno, prijmeni) {
        this.jmeno = jmeno;
        this.prijmeni = prijmeni;
}

Clovek.prototype.pozdrav = function () {
        alert("Zdraví tě " + this.jmeno + " " + this.prijmeni);
}

var karel = new Clovek("Karel", "Chytrý");
karel.pozdrav();
Odpovědět 7. února 14:06
Nesnáším {}, proto se jim vyhýbám.
Avatar
Odpovídá na Michal Žůrek - misaz
Tomáš Pařízek:7. února 14:55

Děkuji, to se mi hodí. Je to něco podobného jako v Pythonu self.

 
Odpovědět 7. února 14:55
Avatar
Odpovídá na Tomáš Pařízek
Michal Žůrek - misaz:7. února 15:12

přesně tak. je to v podstatě totéž. V JS to lze ještě použít sice i jinak, ale to je docela složité.

Odpovědět 7. února 15:12
Nesnáším {}, proto se jim vyhýbám.
Avatar
Odpovídá na Michal Žůrek - misaz
Tomáš Pařízek:8. února 13:07

Jen bych se teda chtěl zeptat na konkrétní věc. Pokud si chci vytvořit takovou malou, řekněme ,,Knihovničku", která se teda jmenuje po mě. Kde jsem udělal chybu zde:

Knihovna: parizek.js

var GameObject = function(color, width, height) {
        this.color = color
        this.width = width
        this.height = height
        }

function GameObject.prototype.create()
{
        var a = document.createElement(div)
        a.style = "height: " + this.height + "; width: " + this.width + "; background-color: " + this.color
        return a
        }

A zde test: test.html

<title>Test</title>
<meta charset="utf-8">
<body id="telo">

<script src="parizek.js"></script>
<script type="text/javascript" >
        plocha = new GameObject("orange", "50", "50")
        plocha.create()
        telo.appendChild(plocha)
</script>
Editováno 8. února 13:08
 
Odpovědět 8. února 13:07
Avatar
Odpovídá na Tomáš Pařízek
Michal Žůrek - misaz:8. února 15:23

Skvělý začátek! Správně je to takhle.

var GameObject = function(color, width, height) {
    this.color = color
    this.width = width
    this.height = height
}

GameObject.prototype.create = function () { // musí se psát takhle
    var a = document.createElement("div") // div musí být v uvozovkách
    a.style = "height: " + this.height + "; width: " + this.width + "; background-color: " + this.color
    return a;
}

a

<title>Test</title>
<meta charset="utf-8">
<body>

<script src="parizek.js"></script>
<script type="text/javascript" >
    var plocha = new GameObject("orange", "50", "50")
    document.body.appendChild(plocha.create()) // musí se vložit to co vrátí nějaký element, tedy metoda create.
</script>
Odpovědět 8. února 15:23
Nesnáším {}, proto se jim vyhýbám.
Avatar
Odpovídá na Michal Žůrek - misaz
Tomáš Pařízek:8. února 15:48

Děkuji, dělám si z toho takovou knihovničku která by mně pomáhala, abych nemusel psát tolik kódu. Teď už budu vědět jak na to.

 
Odpovědět 8. února 15:48
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 21. Zobrazit vše