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

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.

Jsem vývojář na volné noze. Programuji v JavaScriptu a v node.js. Občas něco napíši v PHP.

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

Komentáře: 10

Přehled komentářů

Salko Ipad
Michal Taneček Re: Ipad
Salko Re: Ipad
Martin Hassman Re: Ipad
Michal Taneček Re: Ipad
zbyso FF 27
Michal Taneček Re: FF 27
zbyso Re: FF 27
Jiří Prokop JavaScript Kinetic Scrolling
Leoš Ondra CSS Animace
Zdroj: https://www.zdrojak.cz/?p=11269