Vytváříme kreslicí aplikaci s HTML5 canvasem (dokončení)

Jsme tu opět s návodem k používání canvasu. V dnešním druhém dílu dokončíme naši kreslicí aplikaci, přidáme další kreslicí nástroje (kreslení obdélníků a úseček) a zmíníme řadu nápadů, jak můžete výslednou aplikaci sami vylepšit.

Tento článek je druhým dílem překladu anglického originálu vydaného na Dev.Opera. Jeho první část jste si mohli přečíst před několika dny.

Přidání dalších kreslicích nástrojů

Nyní k aplikaci z minulého dílu přidáme další kreslicí nástroje. Každý z nich bude mít svůj vlastní objekt s příslušnými událostmi.

Nejprve přidáme rozbalovací menu, ve kterém může uživatel mezi jednotlivými nástroji vybírat. Toho docílíme přidáním následujícího kódu:

<body>
<p><label>Drawing tool: <select id="dtool">
  <option value="rect">Rectangle</option>

  <option value="pencil">Pencil</option>
</select></label></p>
<!-- ... -->
</body> 

Dále upravíme náš skript z minulého dílu, aby dokázal pracovat s více nástroji:

// ...
// Aktivní nástroj.
var tool = false;
var tool_default = 'rect';

function init () {
  // ...
  // Získej prvek select.
  var tool_select = document.getElementById('dtool');
  if (!tool_select) {
    alert('Chyba: nenalezen prvek dtool!');
    return;
  }
  tool_select.addEventListener('change', ev_tool_change, false);

  // Aktivujeme výchozí nástroj.
  if (tools[tool_default]) {
    tool = new tools[tool_default]();
    tool_select.value = tool_default;
  }
  // ...
}

// ...
// Obsluha události pro změnu nástroje.
function ev_tool_change (ev) {
  if (tools[this.value]) {
    tool = new tools[this.value]();
  }
}

// Tento objekt obsahuje implementace všech kreslicích nástrojů.
var tools = {};

// Nástroj tužka.
tools.pencil = function () {
  // ...
};

// ... 

To by stačilo (kompletní kód najdete v příloze). Kód výše nastavil obsluhu událostí pro prvek <select>. Implementace všech kreslicích nástrojů je umístěna v objektu tools a proměnná tool obsahuje instanci aktivního nástroje. Funkce ev_tool_change() zajišťuje, aby proměnná tool vždy obsahovala instanci nástroje vybraného uživatelem.

Výhodou našeho kódu je, že každý nástroj může mít svou logiku umístěnou uvnitř instance nezávislou na zbytku kódu. Jakmile je nástroj aktivován, můžete dělat opravdu cokoliv chcete, např. ptát se uživatele na řetězec, číslo nebo jiný vstup.

Vytvořili jsme si tak dobrý základ pro tvorbu dalších nástrojů. Pojďme se tedy podívat na implementaci dalšího kreslicího nástroje.

Kreslení obdélníků

Implementujme tedy nástroj pro kreslení obdélníků a otestujme si výsledek (aktualizovaný skript):

// ...
tools.rect = function () {
  var tool = this;
  this.started = false;

  this.mousedown = function (ev) {
    tool.started = true;
    tool.x0 = ev._x;
    tool.y0 = ev._y;
  };

  this.mousemove = function (ev) {
    if (!tool.started) {
      return;
    }

    var x = Math.min(ev._x,  tool.x0),
        y = Math.min(ev._y,  tool.y0),
        w = Math.abs(ev._x - tool.x0),
        h = Math.abs(ev._y - tool.y0);

    context.clearRect(0, 0, canvas.width, canvas.height);

    if (!w || !h) {
      return;
    }

    context.strokeRect(x, y, w, h);
  };

  this.mouseup = function (ev) {
    if (tool.started) {
      tool.mousemove(ev);
      tool.started = false;
    }
  };
};
// ... 

Implementace tohoto nástroje je jednoduchá a snadno pochopitelná. Obsahuje stejné základní kameny jako nástroj pro kreslení tužkou. V tomto případě si ale uložíme počáteční bod, který potřebujeme pro nakreslení obdélníku při každém pohybu myši.

Zkuste si ale nakreslit dva obdélníky. Vidíte, kde je problém? Jistě, první obdélník je smazán voláním metody clearRect(). Tohle volání nemůžeme odstranit, protože pak by celý nástroj byl nepoužitelný (každý obdélník nakreslený během pohybu myši by zůstal na obrazovce).

Použijeme proto pro náš nástroj pomocný canvas. Iniciační skript přidá nový prvek canvas se stejnými rozměry, jaké má náš původní canvas, a umístí jej přes něj. Všechny nástroje tak musí kreslit do pomocného canvasu a když je jejich operace ukončena, budou nakreslené pixely přeneseny do původního canvasu.

Vyzkoušejte si živou ukázku, ve které už bez problému nakreslíte více obdélníků.

Podívejme se na provedené změny v našem skriptu. Iniciační funkce bude vypadat takto:

// ...
var canvas, context, canvaso, contexto;

function init () {
  // Najdi prvek canvas.
  canvaso = document.getElementById('imageView');
  if (!canvaso) {
    alert('Chyba: Nemůžu najít prvek canvas!');
    return;
  }

  if (!canvaso.getContext) {
    alert('Chyba: neexistuje canvas.getContext!');
    return;
  }

  // Získej 2D context canvasu.
  contexto = canvaso.getContext('2d');
  if (!contexto) {
    alert('Chyba: nelze získat context!');
    return;
  }

  // Vytvoř pomocný canvas.
  var container = canvaso.parentNode;
  canvas = document.createElement('canvas');
  if (!canvas) {
    alert('Chyba: nemůžu vytvořit nový canvas!');
    return;
  }

  canvas.id     = 'imageTemp';
  canvas.width  = canvaso.width;
  canvas.height = canvaso.height;
  container.appendChild(canvas);

  context = canvas.getContext('2d');

  // ...
} 

Nová funkce img_update() vypadá následovně:

// Funkce nakreslí canvas #imageTemp na vrch canvasu #imageView,
// a vymaže canvas #imageTemp. Funkce je zavolána vždy,
// když uživatel dokončí kreslicí operaci.
function img_update () {
  contexto.drawImage(canvas, 0, 0);
  context.clearRect(0, 0, canvas.width, canvas.height);
} 

Po dokončení každé kreslicí operace musíme zavolat funkci img_update(), aby se nakreslené pixely uložily do vytvářeného obrázku. Upravený nástroj tužka by vypadal takto:

// Kreslení tužkou.
tools.pencil = function () {
  // ...
  this.mouseup = function (ev) {
    if (tool.started) {
      tool.mousemove(ev);
      tool.started = false;
      img_update();
    }
  };
}; 

V případě kreslení tužkou i kreslení obdélníku je kreslicí operace ukončena, jakmile uživatel uvolní tlačítko myši. Stačí tedy, když přidáme volání img_update() do obsluhy události  mouseup.

Musíme provést ještě drobnou úpravu v kaskádových stylech našeho dokumentu:

<style type="text/css">
  #container { position: relative; }
  #imageView { border: 1px solid #000; }
  #imageTemp { position: absolute; top: 1px; left: 1px; }
</style> 

Přidaná pravidla zajistí, aby byl dočasný canvas umístěn přes canvas původní.

Kreslení úseček

Když už máme všechno připraveno, je přidávání nových nástrojů velmi snadné. Skript s implementací kreslení úseček bude obsahovat následující kód:

tools.line = function () {
  var tool = this;
  this.started = false;

  this.mousedown = function (ev) {
    tool.started = true;
    tool.x0 = ev._x;
    tool.y0 = ev._y;
  };

  this.mousemove = function (ev) {
    if (!tool.started) {
      return;
    }

    context.clearRect(0, 0, canvas.width, canvas.height);

    context.beginPath();
    context.moveTo(tool.x0, tool.y0);
    context.lineTo(ev._x,   ev._y);
    context.stroke();
    context.closePath();
  };

  this.mouseup = function (ev) {
    if (tool.started) {
      tool.mousemove(ev);
      tool.started = false;
      img_update();
    }
  };
}; 

A to je celé, vyzkoušejte si živou ukázku.

Nástroj je podobný nástroji pro kreslení obdélníků. Funkce mousedown() uloží souřadnice počátečního bodu, které se použijí v metodě mousemove() pro nakreslení úsečky.

A co dál?

Nyní byste měli mít dobrou představu, co je třeba k vytvoření takové kreslicí aplikace. Kromě kreslení do canvasu musíte vzít v úvahu také několik bodů:

  • Aktuální struktura objektu kreslicího nástroje je postavená na událostech. Pro některé nástroje budete potřebovat další události jako jsou pre-activation, post-activation nebo deactivation. Například nástroj „Vlož obrázek“ může po uživateli vyžadovat zadání URL, ale pokud to uživatel zruší, musí skript nějak zrušit aktivaci nástroje.
  • Budete určitě potřebovat další obsluhy událostí. Pouhé tři, které jsme použili, nebudou stačit (vzpomeňme kontextová menu, dvojklik atd.). Přidání dalších událostí do seznamu by mělo být triviální.
  • Budete chtít uchovat historii pro operace Undo / Redo. K tomu můžete zvolit ze tří přístupů: uchovat v paměti nakreslený obrázek po každém kroku, pamatovat si všechny provedené operace (jako makro) nebo kombinace předchozích dvou způsobů. Každý má své pro i proti. První je rychlejší a snazší na implementaci, ale vyžaduje hodně paměti a v případě velkých obrázků bude ukládání historie pomalé. Druhá metoda je komplexnější a pomalejší, když budete ukládat příkazy v historii a znovu je spouštět. Hybridní metoda je nejkomplexnější řešení, ale v závislosti na situaci může být řešením nejrychlejším.
  • Pro řadu kreslicích operací se budou hodit klávesové zkratky. Kreslicí objekt pak bude potřebovat obsluhovat víc událostí.
  • Každá nástroj by měla mít svou vlastní sadu vlastností (např. barva, tloušťka atd.).
  • Můžete zvážit použití SVG spolu s canvasem pro větší spektrum možností.
  • Základem aplikace je její uživatelské rozhraní: příkazy, nástroje, klávesové zkratky a zvládnutí obecných use cases. Vaše aplikace musí být příjemná pro denní používání.

Pokud se chcete naučit víc, podívejte se na open source projekt Paint.Web. Tutoriál, který jste právě dočetli, vychází z kódu použitého v projektu Paint.Web, proto by pro vás neměl být velký problém kódu porozumět.

Tento článek je překladem textu Creating an HTML 5 canvas painting application, jehož autorem je Mihai Sucan a je zde zveřejněn s laskavým svolením Opera Software.

Vystudoval jsem biochemii. Vymyslel jsem a založil Zdroják. Jsem vyhlášeným expertem na likvidaci komentářů. Nejsem váš hodný tatínek, který vás bude brát za ručičku, já jsem zlý moderátor diskusí. Smiřte se s tím!

Věděli jste, že nám můžete zasílat zprávičky? (Jen pro přihlášené.)

Komentáře: 14

Přehled komentářů

Ped Otazky...
Martin Hassman Re: Otazky...
Ped Re: Otazky...
Rado2 Re: Otazky...
Martin Hassman Re: Otazky...
Rado2 Re: Otazky...
Martin Hassman Re: Otazky...
Ped Re: Otazky...
Martin Hassman Re: Otazky...
Jirka Kosek Re: Otazky...
Ken Vas Canvas mne nebere
BFU Re: Canvas mne nebere
norwi explorer
Martin Hassman Re: explorer
Zdroj: https://www.zdrojak.cz/?p=2998