Orchestrarea complexității cu API-ul de animații web
Publicat: 2022-03-10Nu există cale de mijloc între tranzițiile simple și animațiile complexe. Ori ești în regulă cu ceea ce oferă tranzițiile și animațiile CSS, fie ai nevoie dintr-o dată de toată puterea pe care o poți obține. Web Animations API vă oferă o mulțime de instrumente pentru a lucra cu animații. Dar trebuie să știi cum să le gestionezi. Acest articol vă va prezenta principalele puncte și tehnici care vă pot ajuta să faceți față animațiilor complexe, rămânând flexibil.
Înainte de a intra în articol, este vital să fiți familiarizat cu elementele de bază ale API-ului Web Animations și JavaScript. Pentru a fi clar și pentru a evita distragerea atenției de la problema în cauză, exemplele de cod furnizate sunt simple. Nu va fi nimic mai complex decât funcțiile și obiectele. Ca puncte de intrare frumoase în animațiile în sine, aș sugera MDN ca referință generală, seria excelentă a lui Daniel C. Wilson și API-ul CSS Animations vs Web Animations de Ollie Williams. Nu vom parcurge modalitățile de a defini efectele și de a le regla pentru a obține rezultatul dorit. Acest articol presupune că aveți animațiile definite și aveți nevoie de idei și tehnici pentru a le gestiona.
Începem cu o privire de ansamblu asupra interfețelor și pentru ce sunt acestea. Apoi ne vom uita la momentul și la nivelurile de control pentru a defini ce, când și pentru cât timp. După aceea, vom învăța cum să tratăm mai multe animații ca una, învelindu-le în obiecte. Acesta ar fi un bun început în calea utilizării API-ului Web Animations.
Interfețe
API-ul Web Animations ne oferă o nouă dimensiune de control. Înainte de aceasta, tranzițiile și animația CSS, oferind o modalitate puternică de definire a efectelor, aveau încă un singur punct de acționare . Ca un întrerupător de lumină, era fie pornit, fie oprit. Te-ai putea juca cu funcții de întârzieri și relaxare pentru a crea efecte destul de complexe. Totuși, la un moment dat, devine greoi și greu de lucrat.
Web Animations API transformă acest punct unic de acționare în control complet asupra redării . Comutatorul de lumină se transformă într-un întrerupător de intensitate cu glisor. Dacă doriți, puteți să o transformați într-o casă inteligentă, deoarece, în plus față de controlul de redare, acum puteți defini și modifica efectele în timpul rulării. Acum puteți adapta efectele la context sau puteți implementa un editor de animații cu previzualizare în timp real.
Începem cu interfața Animation. Pentru a obține un obiect de animație, putem folosi metoda Element.animate
. Îi oferi cadre cheie și opțiuni și îți redă animația imediat. Ceea ce face, de asemenea, este că returnează o instanță de obiect Animation
. Scopul său este de a controla redarea.
Gândiți-vă la el ca la un casetofon , dacă vă amintiți acestea. Sunt conștient că unii dintre cititori ar putea să nu fie familiarizați cu ce este. Este inevitabil ca orice încercare de a aplica concepte din lumea reală pentru a descrie lucrurile computerizate abstracte să se destrame rapid. Dar lăsați-vă să vă liniștească — un cititor care nu cunoaște bucuria de a derula o bandă cu un creion — că oamenii care știu ce este un casetofon vor fi și mai derutați până la sfârșitul acestui articol.
Imaginați-vă o cutie. Are o fantă în care merge caseta și are butoane pentru redare, oprire și derulare înapoi. Aceasta este instanța interfeței Animation - o casetă care conține o animație definită și oferă modalități de a interacționa cu redarea acesteia. Îi dai ceva de jucat și îți dă controlul înapoi.
Controalele pe care le obțineți sunt în mod convenabil similare cu cele pe care le obțineți de la elementele audio și video. Sunt metode de redare și pauză și proprietatea timpului curent . Cu aceste trei comenzi, puteți construi orice când vine vorba de redare.
Caseta în sine este un pachet care conține o referință la elementul care este animat, definiția efectelor și opțiunile care includ, printre altele, sincronizarea. Și asta este KeyframeEffect
. Caseta noastră este ceva care deține toate înregistrările și informații despre lungimea înregistrărilor. Voi lăsa imaginația publicului mai în vârstă să potrivească toate acele proprietăți cu componentele unei casete fizice. Ceea ce vă voi arăta este cum arată în cod.
Când creați o animație prin Element.animate
, utilizați o comandă rapidă care face trei lucruri. Acesta creează o instanță KeyframeEffect
. Se introduce într-o nouă instanță de Animation
. Începe imediat să-l joace.
const animation = element.animate(keyframes, options);
Să-l defalcăm și să vedem codul echivalent care face același lucru.
const animation = new Animation( // (2) new KeyframeEffect(element, keyframes, options) // (1) ); animation.play(); (3)
Luați caseta (1), introduceți-o într-un player (2), apoi apăsați butonul Redare (3).
Ideea de a ști cum funcționează în culise este acela de a putea separa definiția cadrelor cheie și de a decide când să le redați. Când aveți o mulțime de animații de coordonat, ar putea fi util să le adunați pe toate mai întâi, astfel încât să știți că sunt gata de jucat. Generarea lor din mers și speranța că vor începe să joace la momentul potrivit nu este ceva la care ai vrea să speri. Este prea ușor să rupi efectul dorit prin tragere de câteva cadre. În cazul unei secvențe lungi, se acumulează drag, rezultând o experiență deloc convingătoare.
Sincronizare
Ca și în comedie, sincronizarea este totul în animații. Pentru ca un efect să funcționeze, pentru a obține o anumită senzație, trebuie să fiți capabil să reglați fin modul în care se schimbă proprietățile. Există două niveluri de sincronizare pe care le puteți controla în API-ul Animații web.
La nivelul proprietăților individuale, avem offset
. Offset vă oferă control asupra sincronizarii unei singure proprietăți . Dându-i o valoare de la zero la unu, definiți când va începe fiecare efect. Când este omis, este egal cu zero.
S-ar putea să vă amintiți din @keyframes
în CSS cum puteți utiliza procente în loc de de from
/ to
. Asta este offset
, dar împărțită la o sută. Valoarea offset
este o parte din durata unei singure iterații .
offset
-ul vă permite să aranjați cadre cheie într-un KeyframeEffect
. Fiind o compensare relativă a numărului, se asigură că, indiferent de durata sau rata de redare, toate cadrele cheie încep în același moment unul față de celălalt.
După cum am afirmat anterior, offset
-ul este o parte a duratei . Acum vreau să eviți greșelile mele și pierderea de timp cu asta. Este important să înțelegeți că durata animației nu este același lucru cu durata totală a unei animații. De obicei, sunt la fel și asta te-ar putea deruta și, cu siguranță, m-a încurcat pe mine.
Durata este timpul în milisecunde în care o iterație durează pentru a se finaliza. În mod implicit, va fi egală cu durata totală. Odată ce adăugați o întârziere sau creșteți numărul de iterații într-o animație, durata nu vă mai spune numărul pe care doriți să îl aflați. Este important să înțelegeți să îl folosiți în avantajul dvs.
Când trebuie să coordonați o redare a unui cadru cheie într-un context mai mare, cum ar fi redarea media, trebuie să utilizați opțiunile de sincronizare. Întreaga durată a animației de la început până la evenimentul „terminat” în următoarea ecuație:
delay + (iterations × duration) + end delay
Îl puteți vedea în acțiune în următorul demo:
Ceea ce ne permite să facem este să aliniem mai multe animații în contextul media cu lungime fixă. Păstrând intactă durata dorită a animației, ați putea să o „tampeze” cu delay
la început și delayEnd
la sfârșit pentru a o încorpora într-un context cu o durată mai lungă. Dacă vă gândiți bine, delay
în acest sens ar acționa ca și offset-ul în cadrele cheie. Nu uitați că întârzierea este setată în milisecunde, așa că este posibil să doriți să o convertiți la o valoare relativă.
O altă opțiune de sincronizare care ar ajuta la alinierea animației este iterationStart
. Setează poziția de pornire a unei iterații. Luați demonstrația mingii de biliard. Prin ajustarea glisorului iterationStart
Start, puteți seta poziția de pornire a mingii și rotația, de exemplu, o puteți seta să înceapă să sară din centrul ecranului și să facă ca numărul să fie drept în cameră în ultimul cadru.
Controlați mai mulți ca unul
Când am lucrat la editorul de animație pentru o aplicație de prezentare, a trebuit să aranjez mai multe animații pentru un singur element pe o linie temporală. Prima mea încercare a fost să folosesc offset
pentru a-mi pune animația la punctul de pornire corect pe o linie temporală.
Acesta sa dovedit rapid a fi modul greșit de a folosi offset
-ul. În ceea ce privește această interfață de utilizare particulară, animația în mișcare pe cronologie menită să-și schimbe poziția de pornire fără a schimba durata animației. Cu offset
asta însemna că trebuia să schimb mai multe lucruri, offset
-ul în sine și, de asemenea, să schimb offset
-ul proprietății de închidere pentru a mă asigura că durata nu se schimbă. Soluția s-a dovedit a fi prea complexă pentru a fi înțeleasă.
A doua problemă a venit cu proprietatea transform
. Datorită faptului că poate reprezenta mai multe modificări caracteristice unui element, poate deveni dificil să-l faci să facă ceea ce îți dorești. În cazul dorinței de a schimba aceste proprietăți independent unele de altele, ar putea deveni și mai greu. Funcția de schimbare a scalei influențează toate funcțiile care o urmează. Iată de ce se întâmplă asta.
Proprietatea de transformare poate lua mai multe funcții dintr-o secvență ca valoare. În funcție de ordinea funcției, rezultatul se modifică. Luați scale
și translate
. Uneori este util să definiți translate
în procente, ceea ce înseamnă raportat la dimensiunea unui element. Să presupunem că vrei ca o minge să sară cu exact trei diametre proprii. Acum, în funcție de locul în care plasați funcția de scalare - înainte sau după translate
- rezultatul se schimbă de la trei înălțimi ale dimensiunii originale sau cea scalată.
Este o trăsătură importantă a proprietății transform
. Ai nevoie de el pentru a realiza o transformare destul de complexă. Dar când ai nevoie ca acele transformări să fie distincte și independente de alte transformări ale unui element, îți iese în cale.
Există cazuri când nu puteți pune toate efectele într-o singură proprietate de transform
. Se poate obține prea mult destul de repede. Mai ales dacă cadrele cheie provin din locuri diferite, ar trebui să aveți o îmbinare foarte complexă a unui șir transformat . Cu greu te-ai putea baza pe un mecanism automat, deoarece logica nu este simplă. De asemenea, ar putea deveni greu de înțeles la ce să te aștepți. Pentru a simplifica acest lucru și a păstra flexibilitatea, trebuie să le separăm în canale diferite.
O soluție este să împachetăm elementele noastre în div
-uri pe care fiecare ar putea fi animat separat, de exemplu un div pentru poziționarea pe pânză, altul pentru scalare și al treilea pentru rotație. În acest fel, nu numai că simplificați foarte mult definiția animațiilor, dar deschideți și posibilitatea de a defini diferite origini de transformare, acolo unde este cazul.
S-ar putea părea că lucrurile scapă de sub control cu acel truc. Că înmulțim numărul de probleme pe care le-am avut înainte. De fapt, când am găsit pentru prima dată acest truc, l-am renunțat ca fiind prea mult. M-am gândit că aș putea să mă asigur că proprietatea mea de transform
este compilată din toate piesele în ordinea corectă într-o singură bucată. A fost nevoie de încă o funcție de transform
pentru a face lucrurile prea complexe de gestionat și anumite lucruri imposibil de făcut. Compilatorul meu de șir de proprietăți de transform
a început să ia din ce în ce mai mult timp pentru a fi corect, așa că am renunțat.
S-a dovedit că controlul redării mai multor animații nu este atât de greu cum pare să fie inițial. Îți amintești de la început analogia casetofonului? Ce se întâmplă dacă ai putea folosi propriul tău player care acceptă orice număr de casete? Mai mult decât atât, puteți adăuga câte butoane doriți pe acel player.
Singura diferență dintre apelarea play
pe o singură animație și o serie de animații este că trebuie să iterați. Iată codul pe care îl puteți folosi pentru orice metodă de instanțe de Animation
:
// To play just call play on all of them animations.forEach((animation) => animation.play());
Vom folosi acest lucru pentru a crea tot felul de funcții pentru playerul nostru.
Să creăm acea casetă care să țină animațiile și să le redăm. Puteți crea aceste cutii în orice mod care este potrivit. Pentru a fi clar, vă voi arăta un exemplu de realizare cu o funcție și un obiect. Funcția createPlayer
preia o serie de animații care urmează să fie redate în sincronizare. Returnează un obiect cu o singură metodă de play
.
function createPlayer(animations) { return Object.freeze({ play: function () { animations.forEach((animation) => animation.play()); } }); }
Este suficient să știți să începeți să extindeți funcționalitatea. Să adăugăm metodele pauză și currentTime
.
function createPlayer(animations) { return Object.freeze({ play: function () { animations.forEach((animation) => animation.play()); }, pause: function () { animations.forEach((animation) => animation.pause()); }, currentTime: function (time = 0) { animations.forEach((animation) => animation.currentTime = time); } }); }
createPlayer
cu aceste trei metode vă oferă suficient control pentru a orchestra orice număr de animații . Dar hai să o împingem puțin mai departe. Să facem astfel încât playerul nostru să poată lua nu numai un număr mare de casete, ci și alți playere.
După cum am văzut mai devreme, interfața de Animation
este similară cu interfețele media. Folosind această asemănare, ai putea pune tot felul de lucruri în player. Pentru a ne adapta, să modificăm metoda currentTime
pentru a o face să funcționeze atât cu obiecte animații, cât și cu obiecte care au venit din createPlayer
.
function currentTime(time = 0) { animations.forEach(function (animation) { if (typeof animation.currentTime === "function") { animation.currentTime(time); } else { animation.currentTime = time; } }); }
Playerul pe care tocmai l-am creat este cel care vă va permite să ascundeți complexitatea mai multor div
-uri pentru canalele de animații cu un singur element. Aceste elemente ar putea fi grupate într-o scenă. Și fiecare scenă ar putea face parte din ceva mai mare. Tot ce se poate face cu această tehnică.
Pentru a demonstra demonstrația de sincronizare, am împărțit toate animațiile în trei jucători. Prima este să controlezi redarea previzualizării din dreapta. Al doilea combină animația de sărituri a tuturor contururilor mingilor din stânga și a celei din previzualizare.
În cele din urmă, al treilea este un jucător care a combinat animațiile de poziție ale bilelor într-un container din stânga. Acel jucător permite bilele să se răspândească într-o demonstrație continuă a animației cu aproximativ 60 de cadre pe secundă felii.
Concluzie
Interfețele web precum API-ul Animații web ne expun anumite lucruri pe care browserele le-au făcut tot timpul. Browserele știu cum să randeze rapid prin transmiterea lucrărilor către GPU. Cu API-ul Web Animations, avem control asupra acestuia. Chiar dacă acest control ar putea părea puțin străin sau confuz, nu înseamnă că folosirea lui ar trebui să fie și confuză. Cu o înțelegere a timpului și a controlului redării, aveți instrumente pentru a adapta acel API la nevoile dvs. Ar trebui să poți defini cât de complex ar trebui să fie.
Lectură suplimentară
- „Tehnici practice de proiectare a animației”, Sarah Drasner
- „Proiectare cu mișcare redusă pentru sensibilități la mișcare”, Val Head
- „O interfață de utilizare vocală alternativă la asistenții vocali”, Ottomatias Peura
- „Proiectarea unor sfaturi mai bune pentru interfețele mobile cu utilizatorul”, Eric Olive