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:
- Vyčistíme canvas
- Nastavíme globalAplha
- 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.
Přehled komentářů