ugrás a tartalomhoz

Egyszerű JavaScript animation queue

Max Logan · 2012. Feb. 3. (P), 15.13

Egyik kedves ismerősömnek segédkezem egy egyszerű honlap elkészítésénél. Adott volt egy logó, melyet maga tervezett. Megálmondtott egy egyszerű animációt a logóval kapcsolatban. Mondta, hogy miért ne legyen Flash-ben megoldva, én meg mondtam, hogy miért ne legyen megoldva Flash nélkül?

Így hát nekifogtam a dolognak. Megkaptam az animáció egyes fázisairól a forrásképeket. Ezeket összeillesztettem Photoshopban, mivel az eredeti grafika Corelben készült. Kigondoltam, hogy miként valósítanám meg az animációt HTML, CSS és JavaScript segítségével, majd ez alapján létrehoztam egyetlen GIF fájlt (CSS sprite).

A gondok ezután kezdődtek. Az egyes grafikai objektumokat reprezentáló HTML elemek létrehozása, majd alap „konfigurációs” CSS kódok megírása után pofáncsapott megint az a jelenség, hogy a JavaScriptben nincsen megoldás az egymást követő műveletek „lineáris” végrehajtására.

Hiába vannak egymás után kiadva a parancsok, azok azonos időpillanatban kezdenek futni. A jQuery-nek bár van animation queue megoldása, de ez csak akkor működik, ha az egymás után végrehajtandó animációkat ugyanazon HTML elemen akarjuk futtatni.

Van egy kiegészítés a jQuery-hez, ami kiterjeszti az alap animation queue-t oly módon, hogy meg lehet adni az animate() parancsnak paraméterben, hogy a kérdéses animáció melyik (nevesített) queue-hoz tartozzon.

Szépen működik is, de a csomagban 1.4-es jQuery van, a legújabb 1.7.1-es verzióval nem működik. Mivel nem értek a jQuery-hez, és egyébként sem szívem csücske a JavaScript, egy egyszerű megoldást kerestem a problémára.

Fórumokban keresgélve vagy a callback alapú megoldást ajánlották, vagy pedig a klasszikus setTimeout()-ot. A callbackes történet (amikor egyik animáció callbackje a következő animáció, és így tovább) addig jó, amíg az animation() metódussal operálunk. Nekem viszont kellett a css() is, így ezt a megoldást elvetettem.

Marad nekünk a setTimeout(), ami alap esetben eléggé megnehezíti a dolgunkat. Teszi ezt azért, mert nekünk kellene kézileg kiszámolgatni, hogy az egyes animáció lépések a kezdeti időpillanattól milyen távolságra vannak. Éppen ezen probléma kiküszöbölésére készítettem egy nagyon egyszerű, ám annál hasznosabb animation queue megoldást.

Lássuk a kódot:

var queue = {
    delay: 0,
    callbacks: [],
    
    add: function (callback, duration) {
        this.callbacks.push({
            duration: duration,
            callback: callback
        });
    },
    
    start: function () {
        var length = this.callbacks.length;
        
        for (var i = 0; i < length; i++) {
            setTimeout("eval(" + this.callbacks[i].callback + ");", this.delay);
            this.delay += this.callbacks[i].duration;
        }
    }
}

Semmi különöset nem csinál, csupán annyit, hogy helyettünk végzi a setTimeout()-nak megadandó ezredmásodperc értékek kiszámítását. Ezt úgy tudja megtenni, hogy minden egyes animáció lépéshez megadjuk, hogy mennyi ideig fog tartani.

Valószínűleg nem a legszebb JavaScript kód, de amire szükség volt a feladat során, arra éppen megfelelőnek bizonyult.

 
1

Animált gif?

Poetro · 2012. Feb. 3. (P), 17.35
Azt nem értem, ha már úgyis minden fázis egy kép, és egyébként is GIF-et használsz, miért nem használtál animált GIF-et? Annak is pontosan be lehet állítani, mennyi milliszekundumig legyen az aktuális kép megjelenítve, majd továbblép a következőre, és ha az animáció végére ért, akkor indulhat az egész előről. Ha mondjuk jobb átlátszóság kezelés kellett volna, és PNG-t használsz, akkor jobban megértettem volna, miért is használsz JavaScript-et az animációra. Vagy ha az animáció képkockái között nem lineáris a váltás, hanem mondjuk ugrálni kell az animációban, akkor is megérteném. De az animált GIF kisebb lenne, mint egy sprite, ráadásul JavaScript nélkül is működik, valamint a böngésző natívan kezeli.
7

Re

Max Logan · 2012. Feb. 4. (Szo), 01.25
Nos, most hogy mondod, a logo esetén azt hiszem megoldható lenne a történet animált gif-fel is, mert objektumokat kell megjeleníteni és fade-elni. A fade kérdéses, hogy milyen szépen menne (egy-egy fade időtartalma mondjuk 2,5 mp), de teszek majd egy próbát.

Az egész egyébként azért JavaScript alapokon nyugszik, mert a koncepció egy vizuálisan dinamikusan változó betéteket tartalmazó honlap, de lehetőség szerint Flash nélkül.

Mit értesz az alatt, hogy: „Vagy ha az animáció képkockái között nem lineáris a váltás, hanem mondjuk ugrálni kell az animációban, akkor is megérteném.”?

A logo animálása csak egy felhasználási példa, ami ihlette az animation queue-t. Mivel a jQuery és úgy egyébként a JavaScript nincsen felkészítve ilyen jellegű használatra, azt hiszem, hogy jó szolgálatot tehát másnak is a cucc.
2

Javascript animációval már

Kubi · 2012. Feb. 3. (P), 18.17
Javascript animációval már szívtam egyszer kétszer, pontosabban javascript canvas-al.

A problémák ott kezdődnek, ha egy folytonos mozgást akarok, pl képernyő bal oldaláról egy piros pötty mozgatása a jobb oldalra. A pöttyöt képtelenség folyamatosnak ható mozgásra bírni, időnként ugrál (javascript garbage collection esetén többnyire)

setTimeout, setInterval, vagy az újabb RequestAnimationFrame, mindnél megvan ez a probléma.

Hiába próbáltam a két frame közötti eltelt időt vizsgálni és azzal arányossan növelni egy frame alatt megtett távot, a javascript által visszaadott idő nem pontos.

Direkt játék készítésre készült frameworkökkel csinált demókat is néztem, mindegyiknél észre lehet venni a kis beakadásokat, ha folyamatos mozgás van.

Próbáltátok már ilyesmire használni az animációt? Tudtok valami megoldást esetleg, ami elkerülte a figyelmemet? Hmm, lehet fórum témát kellene csinálnom belőle.
4

Én ezt néztem tegnap, elég

inf3rno · 2012. Feb. 3. (P), 18.57
Én ezt néztem tegnap, elég beszaratós... Már ha ez animációnak számít nálad...

Amúgy HTML5-nek nem pont erre kellett volna megoldást kínálnia?
5

Re

Max Logan · 2012. Feb. 3. (P), 23.19
A nem folyamatos, kicsit megtorpanó, vagy éppen egy-egy pontnál felgyorsuló animáció valóban zavaró. És azt hiszem rá is világít arra, hogy a JavaScript (és a HTML, és a CSS, és a HTTP és úgy egyébként a mai WEB tech alapjai) nem éppen ilyen felhasználásra lett(ek) tervezve (annó).

Jelen esetben a lényeg a flash kiváltása volt. Ahhoz nem értek, ehhez pedig annyira igen, hogy a feladatot meg tudjam oldani. Valamint lesz több olyan anim az oldalon, amihez feleslegesnek éreztem a Flash használatát, ha már egyszer úgyis csak minimális, ám annál látványosabb megoldások lesznek használva.
17

a flash sem sokkal jobb

zzrek · 2012. Feb. 4. (Szo), 18.41
Nem foglalkoztam flash-sel, de sok helyen láttam akadozni azt is, akár igen egyszerű animációknál.
20

flash

zakzag · 2012. Feb. 6. (H), 16.07
lehet hogy nem erre lettek tervezve, viszont a flash erre lett tervezve, és igencsak zavarónak tartom, hogy az i5-ös gépemen némelyik flash lejátszás (és főleg a video-k) olyan szakadozva megy, hogy nézhetetlen.
21

1, akik flash alkalmazásokat

Hidvégi Gábor · 2012. Feb. 6. (H), 17.05
1, akik flash alkalmazásokat írnak, nem minden esetben képzett programozók, hanem például "csak" grafikusok
2, a flash fejlesztői környezet rendkívül erőforrásigényes, gyors gépet igényel, ha nem akarod a fél életedet várakozással tölteni, emiatt a fejlesztők nem szoktak törődni az optimalizálással (náluk jól megy minden)
22

Ebben a formában nem igaz

Poetro · 2012. Feb. 6. (H), 17.14
A Flash fix kepkockákkal dolgozik, azaz az animáció akkor is megpróbálja a beállított képkockaszámot megjeleníteni, ha az lassabb lejátszást eredményez. Tegyük fel, hogy beállítasz 20 képkocka / másodpercet. Ekkor, ha a géped csak 18-at képes valamikor megjeleníteni, akkor el fog húzódni a képsor lejátszása, mivel nem hagy ki képkockákat, ugyanis az ActionScript is képkocka alapú. Ez azt jelenit, hogy akkor fog lefutni a kód, amikor is az aktuális képkocka megjelenik. Ezután megint megjelenik egy képkocka, megint lefuthat kód stb.
A videó lejátszása Flash alatt viszont más tészta, mivel az nem a Flash képkocka metódusa alapján történik normálesetben (bár a kettő szinkronizációja megoldható, csak akkor lehet még darabosabb lesz a videó lejátszása).

Emlékszem, mikor a 700Mhz-es Pentium 3-ason 3D-t szimuláltam háromszögek torzításával és gradiens színezéssel, a gép majd meghalt, de nem tudta lejátszani a mozit 25 képkockánál gyorsabban, hiába állítottam be 50fps-t. Ugyanez a mozi egy akkor viszonylag korszerű 400Mhz-es iMac-en 15fps-sel futott :).

Tanulság: ha valaki intenzívebb kódot akar futtatni Flash-ben (vagy JS-ben), mint amire a gép képes, akkor előfordulhat beszaggatás.
3

eval minek?

Crystal · 2012. Feb. 3. (P), 18.26
hello,

szerintem az eval teljesen fölösleges:
setTimeout(this.callbacks[i].callback, this.delay);
6

Re

Max Logan · 2012. Feb. 3. (P), 23.24
Hm, kivettem az eval()-t és működik. Ahogyan jeleztem is, lehetnek benne „szépséghibák”. Nem ismerem annyira (és őszintén megmondom sok helyen nem is értem) a JavaScript működését, így feltételeztem, hogy egy string-ként átadott kódrészlet futtatásához kell az eval().
8

Nem annyira felesleges, mint

tgr · 2012. Feb. 4. (Szo), 10.22
Nem annyira felesleges, mint inkább káros, mert nélküle át tudsz adni függvényt is a setTimeoutnak, nem csak stringet, ami rugalmasabb, gyorsabb, és nem akadályozza az optimalizálást.
9

Ez így nem igazán "queue",

tgr · 2012. Feb. 4. (Szo), 11.19
Ez így nem igazán "queue", mert az egyes animációk nem akkor indulnak, amikor az előző véget ért, hanem amikor papíron véget kellett volna érnie. A böngészők hajlamosak belepiszkálni az ütemezésbe, ha az oldal éppen nem látható, és emiatt az animációk egymásra csúszhatnak.

A másik probléma, hogy a this javascriptben meglehetősen törékeny:

$(document).ready(queue.start); // nem működik
Igazi queue-t a legelegánsabban szerintem deferredekkel lehet megoldani:

var queue = (function() {
    var first = new $.Deferred(),
        last  = first;
    var self = {
        add: function(call) {
            last = last.pipe(call);
            return self;
        },
        start: function() {
            first.resolve();
        }
    };
    return self;
})();

queue.add(function() {
    $('#foo').css('color', 'red');
    return $('#bar').hide('slow');
}).add(function() {
    return $.get('/');
}).add(function(data, status, xhr) {
    alert(xhr.status);
});

queue.start();
10

Ez mi ez a Deferred? Azt

inf3rno · 2012. Feb. 4. (Szo), 11.41
Ez mi ez a Deferred? Azt látom, hogy van pipe meg resolve az interface-ében, de nekem ennyiből nem jön le, hogy mire hivatott :D (Szándékosan nem akarok utánakeresni, hogy legyen valami tartalom is ebben a blog bejegyzésben.)
11

Kíváncsi vagyok, igazam van-e

H.Z. v2 · 2012. Feb. 4. (Szo), 12.08
Ha már olyat játszunk, hogy nem nézünk utána ;)
Szerintem egy olyan késleltető mechanizmus, ami pl. alkalmas rá, hogy több függvényt indítson szorosan egymás után. (gyakorlatilag egy egyetlen job futtatására alkalmas queue – VMS-ből tudnék erre jó példát :) )
13

Ez a deferred ránézésre sima

inf3rno · 2012. Feb. 4. (Szo), 14.26
Ez a deferred ránézésre sima callback...
15

Lehet. Én csak a nevéből és

H.Z. v2 · 2012. Feb. 4. (Szo), 14.42
Lehet. Én csak a nevéből és az előzményekből indultam ki. :)
12

kötekedés

Raziel Anarki · 2012. Feb. 4. (Szo), 12.14
öööö, ez miért cikk? eval-olt eval()-meg minden... csak izé.

meg nem is animation queue, hanem callback queue. :/
14

Re

Max Logan · 2012. Feb. 4. (Szo), 14.29
Nem cikk, hanem annál egy fokkal light-osabb formátum, blog bejegyzés (és mint ilyen kikerül a címlapra). Mint ítam, nem a legszebb JS kód, ennél fogva kimondatlanul is fenntartottam a hibázás, bénázás jogát. Azért animation, mert arra használtam.

Ha semmi mást nem értem el a publikálással csak annyit, hogy a téma kicsit felszínre került -- mármint a JavaScript queue (jellegű) megoldások --, akkor már megérte...
16

nem is ezzel van a gond

Raziel Anarki · 2012. Feb. 4. (Szo), 18.36
hanem a szakmai "zsűri"-vel, aki nem szólt neked hogy hé, eval! mielőtt kitette.
(nemtom lehet-e szerkeszteni kikerülés előtt)

na mindegy, fel ne vegyétek, kötekedős napom van :D :D
18

Re

Max Logan · 2012. Feb. 4. (Szo), 21.08
Szerkesztették, mert ami a mostani kódban callback-nek van írva, azt én nem annak neveztem...
19

Tehetjük magasabbra a

Joó Ádám · 2012. Feb. 5. (V), 02.19
Tehetjük magasabbra a tartalmi elvárásokat, ennek az lesz a következménye, hogy nem jelennek majd meg bejegyzések (mert vagy elutasítjuk őket, vagy sosem érünk az egyeztetés végére).

Vagy megjelentethetjük a szerző szándéka szerint, és a közösségre hagyjuk, hogy rámutasson az esetleges hibákra.

Hidd el, láttam az evalt.
23

timeline.js

dropout · 2012. Feb. 8. (Sze), 12.38
Próbáld meg ezt inkább: http://marcinignac.com/blog/timeline-js/