JavaScript de animație care funcționează (Partea 3 din 4)

În primul nostru post din această serie, am introdus Procedeul de spriting poate, și cum poate fi folosit pentru a face animații cross-browser ușor și eficient pe web. În cel de-al doilea post, am făcut niște animații simple, chiar dacă au avut o cantitate destul de mare de bug-uri, iar codul nu era gata să meargă live.

Astăzi vom aborda acele bug-uri și ne vom curăța codul, astfel încât să îl putem publica pe o pagină fără teama de a sparge orice cod folosind o metodă numită încapsulare.

Domeniul variabil

Pentru a explica într-adevăr ceea ce a fost atât de rău cu codul din ultimul nostru pas și de ce încapsularea este importantă, trebuie să explicăm mai întâi domeniul de aplicare variabil.

Imaginați-vă că lucrați cu codul de mai jos. Aveți o variabilă utilă în funcție fa asta(), și doriți să utilizați aceeași variabilă într-o altă funcție, fa aia(), dar vă confruntați cu o mică problemă.

 funcția do_this () var very_helpful_variable = 20; ... // Aceasta arată "20", la fel cum vă așteptați la alertă (very_helpful_variable);  funcția do_that () alert (very_helpful_variable); // Dar acest lucru arată "nedefinit"! 

Variabila dvs. funcționează foarte bine în cadrul funcției pe care a fost declarată, dar în afara acestei funcții, este ca și cum nu ar exista niciodată! Asta pentru ca fa aia() nu este în cadrul domeniu a variabilei very_helpful_variable.

Variabilele sunt disponibile numai în interiorul blocului de cod unde sunt declarate, acesta este domeniul lor de aplicare. Odată ce acest bloc de cod este terminat, variabilele sale sunt șterse.

Aruncați o privire la aceste exemple:

 var w = 1; funcția a () var x = 2; funcția b () var y = 3; alert (w); // lucrează alertă (x); // alertează (y); // works alert (z); // undefined alert (w); // lucrează alertă (x); // alertează (y); // alertă nedefinită (z); // undefined funcția c () var z = 4; alert (w); // lucrează alertă (x); // alertă nedefinită (y); // alertă nedefinită (z); // lucrează b (); // undefined alert (w); // lucrează alertă (x); // alertă nedefinită (y); // alertă nedefinită (z); // nedefinit

Mai întâi avem variabila w, care este declarată în afara oricăror funcții. Se numește a variabilă globală, și va funcționa oriunde, deoarece scopul său este întregul document.

Următoarea este variabila X, deoarece este declarat în interiorul funcției A(), aceasta va funcționa numai în interiorul acelei funcții. Aceasta include, de asemenea, funcția de lucru interior b (), de cand b () este în interiorul A().

Cu toate acestea, o variabilă definită în interiorul lui b () (ca y) nu va funcționa în funcție exterioară, deoarece aceasta se află în afara domeniului său de aplicare.

De asemenea, ați putea observa că am încercat fără succes să apelăm la această funcție b () din interiorul funcției c (); numele funcțiilor respectă aceleași reguli ca și celelalte variabile.

O altă întrebare cu JavaScript, dacă începem doar să folosim un nume de variabilă în interiorul unei funcții fără a o declara cu cuvântul cheie var, atunci browserul va presupune că această variabilă ar trebui să fie globală. Deci, dacă nu vă asigurați că întotdeauna vă declarați variabilele cu var , veți ajunge la variabilele globale și nu le veți realiza!

Deci, pentru a rezuma: ori de câte ori declarăm o variabilă, o putem folosi în acel bloc de cod sau în interiorul blocurilor imbricate din interiorul acestuia. Dacă încercăm să o folosim în afara domeniului său de aplicare, valoarea este setată la nedefinit.

Acesta este motivul pentru care în ultimul nostru post, am pus timer variabilă în afara funcțiilor care l-au folosit, deoarece noi aveam nevoie să luăm încă acea variabilă după terminarea funcțiilor.

 var timer; // Aceasta este o funcție variabilă globală run_right (stadiu, stânga) ... timer = setTimeout (funcție () run_right (2, stânga);, 200); ... stop_running () document.getElementById ) .style.backgroundPosition = "0px 0px"; // Dacă "timer" nu a fost setat ca global, nu am putut opri aici clarTimeout (timer); 

Pentru a șterge temporizatorul, aveam nevoie nu mai alerga() să fie în sfera de aplicare a variabilei timer. Deci, am făcut timer o variabilă globală care ar putea fi folosită peste tot, ceea ce ar putea fi în neregulă cu asta?

Problema cu variabilele globale

În orice domeniu de aplicare dat, este imposibil să avem două elemente numite același lucru. Dacă ați încerca să aveți două variabile diferite cu același nume, browserul va scrie doar unul dintre ele. Deci, dacă am avea o variabilă numită timer, și a avut o variabilă separată, de asemenea, numită timer care a fost numit în același scop, unul dintre ele ar fi șters și va lua locul celuilalt și am fi avut un dezastru în codul nostru. Dacă am fi avut variabilă globală denumit timer, atunci ar interfera cu orice altă variabilă numită timer conținute oriunde în pagină - inclusiv orice și toate bibliotecile JavaScript atașate și fișierele externe.

Aceasta este o sursă imensă de dureri de cap, tocmai ați văzut un plug-in JavaScript cu adevărat curat undeva și îl descărcați pe site-ul dvs. și, brusc, toate celelalte plug-in-uri se prăbușesc ... Una dintre plug-in-uri a fost neclară cu globul variabile, sa întâmplat să împărtășești același nume cu altceva, browserul tău se deplasează peste el însuși, iar întreaga pagină ajunge la un punct de tăiere.

Ceea ce face acest lucru și mai rău este că nu veți observa niciodată această problemă când testați codul. Ca și codul nostru de animație din ultimul post, acesta va funcționa foarte bine de la sine. Dar, cu cât mai multe piese adăugați, cu atât mai multe sanse de a avea un conflict de numire și veți fi blocați sortarea prin intermediul a duzin de fișiere JavaScript diferite încercând să dau seama care două nu se obișnuiesc.

Acum vă puteți întreba: "Variabilele globale sunt atât de convenabile! Ce se întâmplă dacă mă uit cu atenție la codul meu și mă asigur că nu am conflicte?" Acest lucru ar putea funcționa într-o lume perfectă, dar în realitate veți avea de multe ori mai mulți oameni care lucrează în diferite părți ale aceleiași pagini sau trebuie să revină și să actualizeze diferite părți ale codului dvs. ani mai târziu sau chiar să aibă cod de la terțe părți pagina dvs. care va fi în afara controlului dvs. (cum ar fi publicitatea plătită).

Deci, pe scurt, nu v-ați dori variabile globale mai mult decât ați dori să vă expuneți cablajul de-a lungul pereților casei dvs. sau mașinilor expuse în mașină, este doar o chestiune de timp înainte să se întâmple ceva care guma lucrările. Din fericire, există o modalitate mai bună de evitare a acestor capcane.

încapsularea

Putem avea toate beneficiile variabilelor globale fără probleme, folosind o tehnică numită încapsulare. Gândiți-vă cum vă construiți un perete în jurul codului dvs. cu doar câteva uși speciale, nimic nu poate intra sau ieși din acel cod decât dacă îl permiteți în mod special.

JavaScript are un tip de variabil numit obiect. Obiectele sunt colecții de date definite de utilizator care conțin informații și funcții (denumite în continuare proprietăţi și metode, respectiv). Vom scrie o funcție care creează un obiect special, care are toate funcțiile de care avem nevoie, "coapte" în el și ne va permite chiar să avem mai mult de un robot fără a trebui să ne duplicăm codul!

Începem prin definirea unei noi funcții cu un nume de variabilă. Va trebui să trecem variabila cu câteva argumente, o să-i transmit elementul HTML pe care îl vom anima, plus câteva valori unice pentru viteza de rulare și înălțimea de salt, astfel încât să putem varia de la robot la robot.

 var RobotMaker = funcție (robot, run_speed, jump_height) // Vom pune toate funcțiile și variabilele noastre în acest domeniu. // Aceasta se află în interiorul peretelui nostru "impenetrabil", astfel încât nimic din această zonă // nu va intra în conflict cu alt cod. întoarce // Înăuntru aici, plasăm toate ușile noastre ... // acestea vor fi singura modalitate prin care orice poate intra sau nu din acest cod. // Și, deoarece aceasta se află încă în același "domeniu" ca și RobotMaker, putem folosi orice variabile menționate mai sus! 

Din moment ce ne vom pune toate funcțiile în interiorul noului nostru "zid", acum ar fi un moment bun pentru a revedea ce erori aveam cu codul original. (Puteți vedea asta în acțiune aici)

S-ar putea să observați că dacă faceți clic pe două butoane de alergare (sau pe un buton de alergare și salt) fără să faceți clic pe Stop butonul J, va continua să facă ambele acțiuni. O a doua problemă este că, indiferent în ce direcție se află J, atunci când dăm clic pe A sari sau Stop buton, el se confruntă de fiecare dată. În cele din urmă, dacă faceți clic pe A sari butonul din nou în timp ce J cade dintr-un prim salt, el va continua să coboare prin pagină într-o buclă nesfârșită.

Pentru a aborda aceste lucruri, trebuie să fim mai specifici cu privire la ceea ce vrem să se întâmple cu fiecare dintre funcțiile noastre:

Când faceți clic pe Executare la dreapta:

  1. Dacă J este sărituri, nu faceți nimic și continuați saltul
  2. Dacă J rulează la stânga, oprește-l să meargă la stânga
  3. Fugiți spre dreapta și animați-vă la cadrul potrivit
  4. Dacă J ajunge la sfârșitul etapei, opriți funcționarea și stați la dreapta

Când faceți clic pe Executare la stânga:

  1. Dacă J este sărituri, nu faceți nimic și continuați saltul
  2. Dacă J se execută corect, oprește-l să meargă la dreapta
  3. Rulați spre stânga și animați-vă la cadrul potrivit
  4. Dacă J atinge sfârșitul etapei, opriți funcționarea și stați în direcția stângă

Când faceți clic pe Oprire în desfășurare:

  1. Dacă J este sărituri, nu faceți nimic și continuați saltul (nu vrem să oprim în aer!)
  2. Dacă rulează la dreapta sau la stânga, nu mai rulați
  3. Dacă vă îndreptați spre dreapta, stați în față. Dacă vă îndreptați spre stânga, stați în partea stângă

Când faceți clic pe Salt:

  1. Dacă J este sărituri, nu faceți nimic și continuați saltul (nu vrem să sară din nou în aer!)
  2. Dacă J se execută la dreapta sau la stânga, nu mai rulați
  3. Porniți saltul. Dacă J este îndreptat spre dreapta, săriți spre dreapta. Dacă vă îndreptați spre stânga, faceți salt spre stânga
  4. Terenul se îndreaptă în aceeași direcție cu saltul

Mai întâi de toate, vom adăuga și alte câteva variabile. Deoarece timerul ar trebui să se comporte diferit pentru a alerga și a sări, vom avea două cronometre separate. De asemenea, dorim să introducem a boolean (true / false) pentru a urmări dacă ar trebui să ne confruntăm cu stânga sau cu dreapta și vom face o etapă variabilă doar pentru a ne salva de la faptul că trebuie să tastăm numele întregului element.

 // În interiorul funcției RobotMaker ... var stage = document.getElementById ('stage'); var run_timer, jump_timer; var face_right = adevărat;

Apoi vom adăuga înapoi în funcțiile noastre pentru a merge bine, a alerga la stânga și a sări. Acestea vor fi în mare parte aceleași, cu câteva diferențe. Mai întâi, toate referințele la elementul pe care îl animăm pot fi înlocuite cu variabila robot (care va fi trecut ca unul dintre argumentele din RobotMaker funcţie). În al doilea rând, am făcut câteva modificări minore ale vitezei de funcționare și ale înălțimii de sărituri în funcții, astfel încât să putem varia aceste valori prin trecerea unor valori diferite. În al treilea rând, folosim face_right variabilă pentru a urmări direcția în care se află J (și în funcția de sărituri, folosind face_right pentru a decide care sprite de sărituri să arate). În cele din urmă, folosim cronometre separate pentru a alerga și a sări.

 // În interiorul funcției RobotMaker ... funcția run_r (fază, stânga) face_right = true; dacă ((la stânga + (15 * run_speed)) < (stage.offsetWidth - robot.offsetWidth)) left = left + (15 * run_speed); robot.style.left = left+"px"; switch (phase) case 1: robot.style.backgroundPosition = "-40px 0px"; run_timer = setTimeout(function()run_r(2, left);, 200); break; case 2: robot.style.backgroundPosition = "-80px 0px"; run_timer = setTimeout(function()run_r(3, left);, 200); break; case 3: robot.style.backgroundPosition = "-120px 0px"; run_timer = setTimeout(function()run_r(4, left);, 200); break; case 4: robot.style.backgroundPosition = "-80px 0px"; run_timer = setTimeout(function()run_r(1, left);, 200); break;   else  robot.style.backgroundPosition = "0px 0px";   function run_l(phase, left) face_right = false; if (0 < robot.offsetLeft - (15 * run_speed)) left = left - (15 * run_speed); robot.style.left = left+"px"; switch (phase) case 1: robot.style.backgroundPosition = "-40px -50px"; run_timer = setTimeout(function()run_l(2, left);, 200); break; case 2: robot.style.backgroundPosition = "-80px -50px"; run_timer = setTimeout(function()run_l(3, left);, 200); break; case 3: robot.style.backgroundPosition = "-120px -50px"; run_timer = setTimeout(function()run_l(4, left);, 200); break; case 4: robot.style.backgroundPosition = "-80px -50px"; run_timer = setTimeout(function()run_l(1, left);, 200); break;   else  robot.style.backgroundPosition = "0px -50px";   function jmp(up, top) if (face_right) robot.style.backgroundPosition = "-160px 0px";  else  robot.style.backgroundPosition = "-160px -50px";  if (up && (robot.offsetTop > (20 * (1 / jump_height)))) top = sus - (sus * .1); robot.style.top = top + "px"; jump_timer = setTimeout (funcția () jmp (sus, sus);, 60);  altfel dacă (sus) up = false; jump_timer = setTimeout (funcția () jmp (sus, sus);, 60);  altfel dacă (! up && (robot.offsetTop < 115)) top = top + (top * .1); robot.style.top = top+"px"; jump_timer = setTimeout(function()jmp(up, top);, 60);  else  robot.style.top = "120px"; if (face_right) robot.style.backgroundPosition = "0px 0px";  else  robot.style.backgroundPosition = "0px -50px";  jump_timer = false;  

Toate aceste variabile și funcții sunt în interiorul "zidului" nostru, așa că acum trebuie să facem "ușile" pentru a putea accesa doar ceea ce avem nevoie. Aceste patru "uși" vor fi obiect metode pentru aceleași patru funcții pe care le-am avut anterior și care vor face referire la funcțiile protejate de mai sus. De asemenea, vom finaliza rezolvarea erorilor prin verificarea fiecărei funcții dacă jump_timer merge, și apoi asigurați-vă că ați goli run_timer. Amintiți-vă, aceste două cronometre se află în sfera de aplicare oriunde în interior RobotMaker () astfel încât să le putem folosi aici. Cu toate acestea, deoarece nu sunt variabile globale, nu vom avea probleme cu ei în altă parte.

 // În interiorul funcției RobotMaker ... return run_right: funcția () if (! Jump_timer || jump_timer == undefined) clearTimeout (run_timer); run_r (1, robot.offsetLeft); , run_left: funcția () if (! jump_timer || jump_timer == undefined) clearTimeout (run_timer); run_l (1, robot.offsetLeft); , stop_running: funcția () if (! jump_timer || jump_timer == undefined) clearTimeout (run_timer); dacă (face_right) robot.style.backgroundPosition = "0px 0px";  altceva robot.style.backgroundPosition = "0px -50px"; , salt: funcția () if (! jump_timer || jump_timer == undefined) clearTimeout (run_timer); jmp (adevărat, robot.offsetTop); 

Acum, că am scris o funcție care creează obiecte, putem folosi de câte ori ne place să facem obiecte care au proprietățile de animație pe care le dorim. În partea de jos a paginii noastre, vom declara două noi RobotMaker obiecte și să le transmiteți elementul pe care dorim să-l animăm, o viteză de rulare și o înălțime de sărituri.

 var j = RobotMaker (document.getElementById ('j'), 1, 1); var j2 = RobotMaker (document.getElementById ('j2'), .8, 5);

Acum nu avem nici un pericol în nimic RobotMaker () funcționează scurgând și interferând cu codul nostru și putem ajunge la funcțiile pe care le dorim prin "ușile" pe care le-am instalat astfel:

 

Deci, acum puteți vedea produsul finit pe pen-ul Hyrgo.

Observați cum nu mai există probleme cu funcțiile care interferează unul cu celălalt și puteți opera fiecare robot în mod individual, fără a afecta celălalt. Encapsularea este o tehnică incredibil de importantă și ar trebui să vă familiarizați cu ea dacă doriți să faceți orice design web interactiv.

Dacă doriți, completați acest cod, comentați complet, și puteți obține sprites folosind următoarele link-uri: iată primii sprites și aici sunt cei doi. Rețineți că, pentru ca același cod să funcționeze cu ambele sprite, a trebuit să fac al doilea sprite în exact același format și dimensiuni ca primul.

Concluzie

Așa că se împachetează partea a treia a spritului! În următoarea și ultima noastră postare, voi înlocui aceste butoane făcând roboții noștri să urmeze mouse-ul în jurul ecranului și vă vor arăta cum să configurați ascultători de evenimente și să activați asistența în toate browserele și dispozitivele touch.

Cod