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

Zdroják » JavaScript » Tvoříme slider obrázků pomocí HTML5 canvasu – animace pohybu, zoom a nelineární animace

Tvoříme slider obrázků pomocí HTML5 canvasu – animace pohybu, zoom a nelineární animace

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

Předchozí díl byl letmým úvodem do canvasu, popsali jsme si základy práce a vytvořili jsme jednoduchou fade-in animaci.

V tomto druhém díle musím prvně vyřešit oprávněné námitky k fallback polyfillu funkce requestAnimationFrame od čtenářů předchozího dílu. Poté se podíváme na animaci pohybu, zoomu a nelineární animaci.

RequestAnimationFrame

Odchylka, způsobená prodlevami při vysokém vytížení prohlížeče (málo výkonné zařízení, flash, příliš mnoho js …), může vyústit v neplynulost animace, proto bude lepší použít polyfill, který s tímto počítá. Použil jsem proto mírně upravený polyfill od Paula Irishe.

Pohyb

Nejelegantnější způsob, jak řešit pohyb, je změna středu. Na rozdíl od umísťování výřezu nemusíme řešit poměry stran canvasu a obrázku. Co vlastně chceme od této animace? Aby se střed obrázku startX, startY změnil za dTime na endX, endY.

Abychom mohli tuto animaci provést, potřebujeme znát následující:

  1. Souřadnice počátku startX, startY
  2. Souřadnice konce endX, endY
  3. Délku animace duration
var config = [
	    2220		// startX
	  , 1700		// startY
	  , 1820		// endX
	  , 1850		// endY
	  , 3000		// duration[ms]
];

Kód pro tuto animaci bude velmi podobný předchozímu, jen se změní konstruktor a metoda, která vykresluje obrázek.

Konstruktor

Jediná změna oproti předchozímu příkladu je, že místo předávání duration předáme pole o 5 parametrech.

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

Animace

V předchozím díle jsem zvolil nešťastně názvy metod (preFade, Fade, postFade), proto je přejmenuji na preAnim, anim, postAnim.

Metoda pro vykreslení má jako paramerty callback a dTime, který nám předává funkce requestAnimationFrame. Pro další příklady (zejména nelineární animace) chceme, aby tato metoda byla funkcí času. To by nám tento jednoduchý příklad komplikovalo, proto si tuto hodnotu dopočítáme přímo v této metodě:

var fTime = (dTime - this.animStart) / this.config[4];

Teď již potřebujeme pouze dopočítat souřadnice pro funkci drawImage canvasu.

Funkce drawImage nepracuje se středem, proto musíme dopočítat souřadnice pro vykreslování, což není nic jiného než počátek + přírůstek – rozměr canvasu / 2.

Počátek jsme předali konstruktoru, přírůstek je funkce času rozdílu počátku a konce, rozměr canvasu taky víme.

var coords = [
  this.config[0] - this.canvas.width / 2 + (this.config[0] - this.config[2]) * fTime
  , this.config[1] - this.canvas.height / 2 + (this.config[1] - this.config[3]) * fTime
];

Celá metoda potom bude vypadat takto:

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

	  var fTime = (dTime - this.animStart) / this.config[4];

	  if (fTime > 1) {
		    fTime = 1;
	  }

	  this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);

	  var coords = [
		this.config[0] - this.canvas.width / 2 + (this.config[0] - this.config[2]) * fTime
		, this.config[1] - this.canvas.height / 2 + (this.config[1] - this.config[3]) * fTime
	  ];

	  this.ctx.drawImage(
		    this.img
		  , coords[0], coords[1]
		  , this.canvas.width, this.canvas.height
		  , 0, 0
		  , this.canvas.width, this.canvas.height
  );

	  if (fTime == 1) {
		    callback();
	  } else {	
		    requestAnimationFrame(this._anim.bind(this, callback));
	  }

	  return;
}

Zdrojový kód

Na výsledný kód se můžete podívat zde.

Zoom

Zoom je další z jednoduchých animací, na kterou se v tomto díle podíváme. Co je to Zoom? Je to změna měřítka v čase.

Abychom mohli tuto animaci provést, potřebujeme znát následující:

  1. Souřadnice středu obrázku centerX, centerY
  2. Počáteční měřítko startScale
  3. Koncové měřítko endScale
  4. Dobu animace duration
var config = [
	    2220		// centerX
	  , 1700		// centerY
	  , 1		   // startScale
	  , 1.5		 // endScale
	  , 3000		// duration[ms]
  ];

Konstruktor se nám nezmění, pouze budou jiné prvky pole, které předáváme.

Animace

Předchozí animace vykreslovaly obrázek v měřítku 1:1, proto byl třetí a čtvrtý parametr pro funkci drawImage rozměr canvasu. Při této animaci již nelze toto použít, proto bude mít pole coords 4 prvky, a to: startX, startY, sumX, sumY.

Výpočet počátku je jednoduchý, jako základ použijeme předchozí příklad a přidáme korekci měřítka.

var zoom = this.config[2] + fTime * (this.config[3] - this.config[2]);

Celá metoda poté bude vypadat takto:

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

	  var fTime = (dTime - this.animStart) / this.config[4];

	  if (fTime > 1) {
		    fTime = 1;
	  }

	  this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
	  var zoom = this.config[2]+ fTime*(this.config[3] - this.config[2]);

	  var coords = [
		  this.config[0] - 1.5*this.canvas.width + (this.canvas.width*zoom)
		, this.config[1] - 1.5*this.canvas.height + (this.canvas.height*zoom)
		, this.canvas.width - 2*(this.canvas.width*zoom - this.canvas.width)
		, this.canvas.height - 2*(this.canvas.height*zoom - this.canvas.height)
	  ];

	  this.ctx.drawImage(
		      this.img
		    , coords[0], coords[1]
		    , coords[2], coords[3]
		    , 0, 0
		    , this.canvas.width, this.canvas.height);

	  if (fTime == 1) {
		    callback();
	  } else {	
		    requestAnimationFrame(this._anim.bind(this, callback))		
	  }
	  return;
}

Zdrojový kód

Na výsledný kód se můžete podívat  zde.

Nelineární animace

V předchozích dvou příkladech jsme změnu stavu počítali jako funkci času s intervalem <0,1>. Není nic jednoduššího než předat naší třídě funkci, která se postará o výpočet potřebného.

Použijme například rovnici:

y = 0.1 (11 - cos x)

Využijeme interval <0,π>, proto x z rovnice bude náš relativní čas fTime * π. (Goniometrické funkce v Math knihovně pracují s radiány). Funkce, kterou předáme v konfiguraci, proto bude vypadat takto:

function (fTime) {
	  return 0.1 * (11 - Math.cos(Math.PI * fTime));
}

Výsledná metoda bude prakticky stejná jako v předchozím případě, jen se změní výpočet měřítka.

if (typeof this.config[2] === 'function') {
	  zoom = this.config[2](fTime);
} else {
	  zoom = this.config[2] + fTime * (this.config[3] - this.config[2]);
}

Takto napsaná animace působí plynulejším dojmem, na začátku zrychluje a na konci brzdí. Navíc nám tento callback umožňuje provádět prakticky jakékoli animace, stačí si jen připravit potřebnou funkci.

Zdrojový kód

Na výsledný kód se můžete podívat zde.

Závěrem

Z předchozího útržku kódu je jasné, že se při každé iteraci zbytečně kontroluje typ třetího indexu pole (počítáme od nuly), při předání callbacku je další parametr null. Navíc takto napsaný skript neumožňuje spouštět více animací.

Takto předané konfigurace se zbavíme v příštím díle, kdy si začneme připravovat kód pro slider, tím rovnou vyřešíme problém s typovou kontrolou v iteraci a zbavíme se do očí bijícího null.

Komentáře

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

Tie ukazky nefunguju na mojom iPade.

Salko

Chybová hláška nie ja žiadna, zobrazi sa obdĺžnik s červeným rámom (ako na desktope), ale bez obsahu, teda bez obrázku. Po chvílke vypíše počet krokov animácie 355, čas anim: 3sec, proste ako na klasickom desktope (FF), ale obrázok nevidím žiaden. To isté robí aj iPhone IOS 7.0.5, Safari. A práve som skúsil aj Chrome pre IOS, výsledok ten istý.
Tvári sa to, že to niečo robilo, ale samotný obrázok nie je vidno.

Martin Hassman

Kolega to zkusil na iPhonu a taky mu to tam neběží. Michale, zkus připravit verzi, která bude podrobněji logovat, co se dějě, ať to rozlouskneme. Doporučuji hlavně zkontrolovat, zda proběhne načtení obrázku (to by mohlo na mobilu trvat logicky déle, ale kolegovi se stránka spustila hned, jakoby se na načtení obrázku nečekalo), tj. this.img.onload = this._beforeAnim.bind(this,null); a následně co se vlastně děje okolo drawImage (zda se to vůbec volá, případně co to vykresluje, zda to není jen bílý prázdný pixel).

zbyso

Ve Firefox 27 je animace dosti priserna – mam na mysli tento odkaz http://ukazky.zdrojak.cz/slider/02/nonlinear.html
V Chrome vse ok, jeste dodam, ze testovano na OS Windows 7

zbyso

Ted jsem to znovu zkusil v 27.0.1 a cela animace neprobiha plynule, ale tese se obraz nahoru dolu o nekolik pixelu, navic obrazek behem animace nedrzi v tom ohraniceni. Bliz uz ti to asi popsat nedokazu ;)

Leoš Ondra

Technologie dozrála natolik, že nepotřebujete nejen knihovny a frameworky, ale prakticky ani JavaScript, ani vanilla ani jiný, dnes máme CSS Animations, případně (na jednodušší věci) Transitions.

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.