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

Zdroják » JavaScript » Tvoříme slider obrázků pomocí HTML5 canvasu

Tvoříme slider obrázků pomocí HTML5 canvasu

Články JavaScript

Na internetu najdete milion a jedna verzí různých sliderů obrázků, které jsou implementovány napříč spektrem technologií a frameworků. Technologie dozrála do doby, kdy k vytvoření slideru nepotřebujete žádné knihovny, jen JavaScript a canvas, jen vanilla JavaScript.

Komu je seriál určen

Tímto článkem začínáme seriál, který má dvě cílové skupiny:

  • ti, kdo chtějí mít pěkný slider obrázků
  • ti, kdo si chtějí něco s canvasem vyzkoušet

Cíl seriálu

Tento mini seriál v pár článcích provede čtenáře tvorbou slideru za pomoci canvasu. Nejprve se podíváme na samotné animace, kde bude brán zřetel hlavně na jednoduchost. Následně z animací sestavíme jednoduchý slider, který bude použitelný na webové prezentaci. Dalším cílem bude přepsání kódu tak, aby mohly animace běžet souběžně.

Implementace

Do psaní jsem se pouštěl s následujícími požadavky:

  • Fade in na začátku
  • Animace pohybu
  • Cross fade mezi obrázky

Canvas

Prvně potřebujeme canvas, tak si jej přidáme do dokumentu.

<canvas id="myCanvas">No canvas MSG.</canvas>

Více informací o canvasu naleznete na MDN: Canvas, nebo zde na zdrojáku: Tag canvas.

Fade in

Fade animace je asi nejznámější. Tento přechod implementují snad všechny JS frameworky, které jsou zaměřeny mimo jiné i na UI. Z tohoto důvodu tímto přechodem začneme. Co to vlastně fade je? Není to nic jiného, než změna průhlednosti v čase.

Abychom mohli tuto animaci provést, potřebujeme znát tři věci: obrázek, canvas a délku animace. Vytvoříme si proto třídu, ve které si připravíme metodu pro nastavení konfigurace. Je to sice pro fade efekt značný overkill, ale s touto animací pouze začínáme.

var Anim = function() {
 this.timeLog = [];
}

Anim.prototype.configure = function(imgUri,animDuration,canvas) {
 this.imgUri = imgUri;
 this.animDuration = animDuration;
 this.canvas = document.getElementById(canvas);
 return;
}

Samotný fade můžeme řešit pomocí property globalAplha, který se nám nabízí v 2d kontextu canvasu. Jak už napovídá název, bude mít něco společného s průhledností. Hned ze začátku tu mám menší varování: property globalAlpha opravdu není globální! (Toho využiji při cross Fade animaci.)

Samotná fade animace je smyčka, s těmito kroky:

  1. Vyčistíme canvas
  2. Nastavíme globalAplha
  3. Vykreslíme obrázek

Vyčištění canvasu

K tomu nám slouží metoda clearRect (mimo jíné), ta očekává 4 parametry : startX,startY,dX,dY.

Nastavení globalAlpha

Zde není co řešit, property je float, prostě jí nastavíme hodnotu v rozmezí 0-1.

Vykreslení obrázku

K vykreslení obrázku do kontextu slouží metoda drawImage(), ta přijímá 9 parametrů:

  • Instance obrázku (html element)
  • 4 parametry pro obrázek (startX,startY,dx,dy)
  • 4 parametry pro kontext (startX,startY,dx,dy)

Řízení běhu

K řízení běhu použijeme funkci requestAnimationFrame, která se postará o volání dalšího kroku animace ve chvíli, kdy je to možné. Jejímu popisu se věnuje MDN.

Internet Explorer implementuje tuto funkci pouze ve verzi 10+, proto si musíme ještě napsat fallback pollyfill pro IE9 a starší. Existuje několik hotových polyfillů, já jsem si ale napsal svůj, z jednoho prostého důvodu: Nechci mít jako parametr dTime vysoké hodnoty unix timestamp, lépe se potom čte průběh animace při debugování. Samozřejmě nesmíme zapomenout na prefixy různých prohlížečů.

function(w){
 "use strict";
 w.requestAnimationFrame = requestAnimationFrame || mozRequestAnimationFrame 
 || webkitRequestAnimationFrame || msRequestAnimationFrame ||
 (function(){
  var startTime = new Date().getTime();
  return function(callback) {
   var time = new Date().getTime();
   var dTime = time - startTime;
   window.setTimeout(callback.bind(callback,dTime),17)
   return;
  }
 }())
}(window))

Číslo 17 není magická kontanta, velká část zobrazovacích zařízení funguje s frekvencí 60Hz, z tohoto důvodu je ideální tick animace 16.6ms, ale setTimeout přijímá integer.

Sestavení kódu

Naše třída má zatím pouze metodu pro předání konfigurace a konstruktor. Nyní si připravíme canvas, nastavíme mu globalAlpha na 0 a načteme si obrázek.

Anim.prototype.run = function() {
 this.ctx = this.canvas.getContext('2d');
 this.ctx.globalAlpha = 0;

 this.img  = new Image();
 this.img.src = this.imgUri;
 this.img.onload = this._beforeFade.bind(this,null);
 return;
}

Předchozí metoda má nastavený callback pro onload event, ten směruje na metodu _beforeFade. Tato metoda zavolá requestAnimationFrame, abychom měli relativní čas začátku animace, a poté spustí samotnou animaci.

Anim.prototype._beforeFade = function(startTime) {
 if(!startTime)
  requestAnimationFrame(this._beforeFade.bind(this))
 this.animStart = startTime;
 requestAnimationFrame(this._fade.bind(this,this._afterFade.bind(this)))
 return;
}

Samotná animace je pak pouze rekurzivní volání metody, která vyčistí canvas, nastaví globalAlpha a vykreslí obrázek.

Anim.prototype._fade = function(callback,dTime) {
 this.timeLog.push(new Date().getTime());

 var alpha = 1/this.animDuration * (dTime - this.animStart);  
 if(alpha > 1) alpha = 1;

 this.ctx.clearRect(0,0,this.canvas.width,this.canvas.height);
 this.ctx.globalAlpha = alpha;
 this.ctx.drawImage(this.img,0,0,this.img.width,this.img.height
  ,0,0,this.canvas.width,this.canvas.height)
 if(alpha == 1) {
  callback();
 } else {
  requestAnimationFrame(this._fade.bind(this,callback))  
 }
 return;
}

Nakonec se zavolá callback, který vypíše statistiku animace.

Anim.prototype._afterFade = function() {
 var log = document.getElementById("log");
 var tmpl = [];
 var timeLog = this.timeLog;
 tmpl.push("Počet kroků animace: ",timeLog.length,"<br/>");
 tmpl.push("Čas animace v s: ", (timeLog[timeLog.length-1]-timeLog[0])/1000,"<br/>");
 tmpl.push("Prum. fps: ",timeLog.length/((timeLog[timeLog.length-1]-timeLog[0])/1000));
 log.innerHTML = tmpl.join("");
 return;
}

Závěr a zdrojový kód

Tímto máme napsaný jednoduchou fade-in animaci za použití canvasu – podívejte se na výsledný kód. V dalším dílu se podíváme na animaci pohybu a zoomu a vytvoříme nelineární animaci.

Komentáře

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

paradni tutorial, jen tak dal :)

Ondřej Žára

Ahoj,

1) v polyfillu se volá callback s časem, který odpovídá rozdílu mezi zavoláním rAF a inicializací polyfillu. Neměl by ten parametr spíš odpovídat rozdílu aktuálního času a toho inicializačního? Protože díky němu pak můžeme vyrovnat nerovnoměrnost časového kroku (časování rAF si řídí prohlížeč sám); v aktuálním řešení je „zpoždění“ způsobené setTimeout ignorováno.

2) co přesně na globalAlpha není globální? Nastavená průhlednost se přeci aplikuje na všechny pixely ve všech operacích nad kontextem…

Ondřej Žára

To by stejnou logikou mohl člověk očekávat, že po zavolání ctx.fillStyle = "red" se dříve černý obdélník přebarví na červenou. Vlastnosti (tuším, že tak se česky překládá „property“) jen nastavují parametry pro následné operace (metody), ale samy od sebe nic nevykreslují…

Martin Hassman

Myslím, že tohle je spor o chápání slova globální. Soouhlasím s tím, že věta použitá v článku je nepřesná a matoucí. Autor upozorňuje, že nastavení globalAlpha nezmění průhlednost celého aktuálního canvasu (naopak by to tak bylo, kdybychom nastavili opacity pomocí CSS pro celý canvas), ake týká se následujících vykreslovacích metod. Z té použité formulace to není přesně znát.

keff

Je otázka, o kolik se bude lišit chyba třeba po 10^6 iteracích.

O to nejde, jde o jitter – hlavne pri pohybu objektu (napr. slide zleva doprava) staci milisekundy k tomu aby nam prestal pohyb pripadat plynuly – a par milisekund je v prostredi kde treba jedno jadro uz vytezuje flash a k tomu browser ceka na disk kvuli nejakemu cache requestu opravdu lehke nabrat.

Miloš

Nějak mě uniká, proč se používá canvas a ne nějaký normální html tag třeba IMG nebo obrázek v pozadí na DIV. Má canvas nějaké výhody ?

Pavel Lang

Canvas má v JavaScriptu vlastní API, které umožňuje získat kontext, který může být 2D (CanvasRenderingContext2D) nebo 3D (WebGLRenderingContext)

Pavel

Tak nevím, zkusil jsem odkaz na „výsledný kód“ na PC (Chrome) a vše bylo OK. Pak jsem ale zkusil totéž na tabletu (iPad) a Safari ani Chrome se nechytali – zobrazili jen border okolo kanvasu a nic víc. Takže hezké, ale prakticky nepoužitelné…

Martin Hassman

Díky za upozornění, bude fajn si v dalších dílech odzkoušet i funkci na tabletech. Celkem věřím, že pokud je tam nyní nějaký problém, tak bude překonatelný.

Jan Růžička

Skvělý článek, moc jsi mi pomohl, ale jen tě chci upozornit, že (ale to samozřejmě je velice snadno přehlédnutelné) jsi napsal globalAplha místo globalAlpha.

Michal Perutka

Jak jsem právě zjistil při ladění v IE9 (když jsem se divil, proč se animace nekoná, zato procesor jede na 100% ;), polyfill chybně volá callback funkci s parametrem dTime (rozdíl časů), správně ji má volat s aktuálním časem (viz definice funkce requestAnimationFrame).

Také volat new Date().getTime() mi přijde zbytečné, stačí Date.now().

Jinak díky za velice užitečný článek.

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.