Generátor funkciók és kérés-animáció-keret

"A reggeli nap úgy süt, mint egy piros gumilabda"

— Simon Pál

Általában nem vagyok az az ember, aki hosszasan beszélne arról, hogy JavaScriptet használjon egy objektum stílusának manipulálására a DOM-on. Egyike vagyok azoknak a puristáknak, akik úgy vélik, hogy ha meg akarod változtatni a stílusodat, akkor inkább a CSS-ben. Az ilyen dogmatikus gondolkodás azonban elég gyorsan összeomlik, ha olyasmivel szembesülünk, amire a CSS egyszerűen nem képes.

Az animációk CSS-réteghez történő hozzáadásával kapcsolatos előrelépések ellenére még mindig alapvetően nincs tisztában az alkalmazásunk állapotával, aminek hatással lehet és kell is lennie animációinkra. De hogyan érhetjük el a „CSS-kulcskockák”-hoz hasonló deklaratív könnyedséget, amikor animációinkat JavaScriptben írjuk? Azt hiszem, a következők némi fényt vethetnek…

Generátor funkciók

A legtöbb animáció egy egyszerű szabályon alapul, amely átalakítja az összetevő állapotát az animációs hurok egyes képkockáihoz. A panel balról becsúszik – vagy a bal oldali értékét idővel növeli. Egy gomb eltűnik a létezéséből – vagy idővel fokozatosan csökkenti átlátszatlanságát. Ez azt jelenti, hogy bármilyen animációt definiálhatunk, ha rendelkezünk (1) a kezdő tulajdonságaival és (2) azokkal a szabályokkal, amelyeket ezekre a tulajdonságokra kell alkalmaznunk. Mit használhatunk, ha ilyen értékek tartományát szeretnénk generálni, kulcskockák halmazát létrehozva? Generátor funkciók!

A „generátor funkciók” a JavaScript egyik erősebb és ritkábban használt funkciója. Ugyanazok a „lusta” jellemzők teszik lehetővé őket, amelyek lehetővé teszik számunkra, hogy végtelen, ismételhető struktúrákat hozzunk létre. Felfoghatja őket egy normál függvénynek, de olyannak, ahol a return utasítás nem fejezi be a függvény végrehajtását. Ehelyett többször is return vagy hozam (mivel ez a speciális kulcsszó), mindegyik a sorozat következő elemét jelenti. A funkció engedélyezéséhez a létrehozásuk feltételeit (a számukra megadott argumentumokat) egy zárt hatókörbe foglalják, amelyre hivatkozhat az egyes egymást követő értékek generálásakor.

Ezt az erőt alkalmazhatjuk JavaScript-animációinkra, ha minden stíluskészletre úgy gondolunk, mint a következő értékre egy olyan szerkezetben, amelyet átismételünk, és alkalmazzuk az objektumunkra. Ez az általános minta valahogy így nézhet ki…

function* getNextStyles(node) {
    let initialStyles = {
        // ... whatever you need to keep track of ...
    }
    while (true) {
        yield {
            // ... whatever the next set of styles will be!
        }
    }
}

Könnyen felhasználhatjuk ezt a mintát a „balról becsúszás” példánk újraalkotására, ami CSS kulcskockákkal könnyen megvalósítható.

function* getNextStyles(node) {
    let bb = node.getBoundingClientRect();
    let left = bb.left;

    while (left < 500) {
        left += 1;
        yield {
            "left": `${left}px`
        }
    }
    return null;
}

Itt az általunk rögzített kezdeti állapot a bal oldali érték, és az animáció részeként iteratív módon változtatjuk. Itt érdemes megjegyezni, hogy a „while true” szerkezet nem fogja átvenni a fő szálunkat, mert a yield kulcsszó feladja a szál irányítását a generátorunk következő meghívásáig. Ha végeztünk, rendelkeznünk kell a DOM-csomópontunkhoz szükséges kiszámított stílusokkal. De ha már megvannak az új stílusaink, mit kezdjünk velük?

Animációs keret kérése

A Request Animation Frame (RAF) egy fantasztikus JS-funkció, amely lehetővé teszi számunkra, hogy részletes frissítéseket hajtsunk végre a DOM egyik összetevőjén anélkül, hogy átvennénk a főszálat. Egyszerűen biztosítunk neki egy olyan funkciót, amelyet szeretnénk, és a RAF a lehető leghamarabb felhívja. Ez megakadályozza, hogy útjába álljunk a felhasználói felület eseményeinek. Az általános minta itt egy olyan funkció létrehozása, amely megkapja a következő stíluskészletet, alkalmazza azokat, és megkéri a RAF-ot, hogy tartsa a golyót. Valahogy így nézhet ki…

function applyStyles() {
    let genStyle = styleGenerator.next().value;
    for (let key in genStyle) {
        target.style[key] = genStyle[key];
    }
    requestAnimationFrame(applyStyles);
}
applyStyles()

Itt kezdődik a móka…

Komolyan azonban, ha csak balról csúsztat be egy elemet, amire szüksége van, akkor jobb, ha CSS kulcskockákat használ. Ugyanazt a funkciót újrahozhatnánk, ha generátorunknak átadunk egy kezdő és egy befejező állapotot, és megkérjük, hogy pótolja a hiányosságokat. Itt azonban lehetőséget látunk arra, hogy túllépjünk a CSS-animációs szabványokon, mivel saját időzítési függvényeinket határozhatjuk meg. (Ebből „a színvonal alapos”, de nem teljes).

Számomra azonban még érdekesebb olyan információk felhasználása, amelyek egyébként nem lennének elérhetőek a CSS-animációkban, például az egér pozíciója vagy két objektum távolsága. Ebben a bemutatóban két animációs funkciót hoztam létre, amelyek csak ezekre az értékekre támaszkodnak. Ez a piros (gumi?) golyó követi a kurzort a képernyőn. Ha rákattint, figyelembe veszi a nézetablak szélétől mért távolságát, lehetővé téve az ugrálást. Kipróbálhatja itt, vagy megnézheti a kódot a GitHubon.

Mi a helyzet néhány egzotikusabb példával? Például mi lenne, ha az egész szöveget kivennénk egy elemből, majd karakterről karakterre visszaadnánk, írógépes hatást produkálva? Továbbmenve, mi lenne, ha egy generátor függvényt használna, hogy véletlenszerűen kitöltse/eltávolítsa az értékek átlátszatlanságát egy kép bittérképében, generáljon egy képet abból a bittérképből, majd „maszkként” alkalmazza a célpontja felett? Nem tudnánk DOM-csomópontjainkat pixelezni? Ez is szórakoztatóan hangzik…

Következtetés

Ismétlem, nem támogatom ennek a mintának a használatát minden felhasználói felület animációs problémájában. CSS-animációink valamiért vannak, és ez egy nagyon jó kalapács. De nem minden eszköz szög. Vannak bizonyos esetek, amikor ez a generátor/RAF kombináció hasznos lehet – különösen, ha animációink egy alkalmazás-specifikus állapotból származnak. De amellett, hogy funkcionális, csak néhány nagyon klassz animációt készít.