JavaScript-animatie die werkt (deel 3 van 4)

In onze eerste post in deze serie hebben we geïntroduceerd spriting, en hoe het kan worden gebruikt om eenvoudige en effectieve cross-browseranimatie op internet te maken. In het tweede bericht kregen we een aantal eenvoudige animaties in gebruik, hoewel ze behoorlijk wat bugs hadden en de code zeker nog niet klaar was om live te gaan.

Vandaag gaan we deze bugs aanpakken en onze code opschonen, zodat we deze op een pagina kunnen publiceren zonder de code te laten crashen met behulp van een methode genaamd inkapseling.

Variabel bereik

Om echt uit te leggen wat er zo mis was met de code in onze laatste stap, en waarom inkapseling belangrijk is, moeten we eerst een variabel bereik uitleggen.

Stel je voor dat je met de onderstaande code werkt. U hebt een nuttige variabele in uw functie doe dit(), en je zou diezelfde variabele in een andere functie willen gebruiken, doe dat(), maar je loopt een klein probleempje tegen.

 function do_this () var very_helpful_variable = 20; ... // Dit toont '20', net zoals je alert verwacht (very_helpful_variable);  function do_that () alert (very_helpful_variable); // Maar dit toont 'undefined'! 

Je variabele werkt geweldig binnen de functie die hij heeft gedeclareerd, maar buiten die functie is het alsof hij nooit heeft bestaan! Dit is zo omdat doe dat() is niet binnen de strekking van de variabele very_helpful_variable.

Variabelen zijn alleen beschikbaar binnen het codeblok waar ze worden gedeclareerd, dit is hun reikwijdte. Zodra dat codeblok is voltooid, worden de variabelen gewist.

Bekijk deze voorbeelden:

 var w = 1; function a () var x = 2; functie b () var y = 3; alert (w); // werkt waarschuwing (x); // werkt waarschuwing (y); // werkt alert (z); // undefined alert (w); // werkt waarschuwing (x); // werkt waarschuwing (y); // undefined alert (z); // undefined functie c () var z = 4; alert (w); // werkt waarschuwing (x); // undefined alert (y); // undefined alert (z); // werkt b (); // undefined alert (w); // werkt waarschuwing (x); // undefined alert (y); // undefined alert (z); // undefined

Eerst hebben we de variabele w, die buiten de functies wordt verklaard. Het wordt een a genoemd globale variabele, en het werkt overal, omdat het het hele document beslaat.

De volgende is de variabele X, omdat het binnen de functie wordt verklaard een(), het werkt alleen in die functie. Dit omvat ook de interne functie b (), sinds b () is binnenkant van een().

Een variabele gedefinieerd binnen in b () (net zoals Y) werkt niet in de buitenste functie, omdat dat buiten de reikwijdte valt.

U kunt ook opmerken dat we tevergeefs hebben geprobeerd om de functie te bellen b () van binnenuit de functie c (); functienamen volgen dezelfde regels als andere variabelen.

Een andere gril met JavaScript, als we binnen een functie een variabele naam beginnen te gebruiken zonder deze met het trefwoord te declareren var, dan zal de browser aannemen dat die variabele globaal moet zijn. Dus, als u er niet zeker van bent dat u altijd uw variabelen declareert met de var sleutelwoord, dan krijg je mondiale variabelen en realiseer je je dat niet!

Kort samengevat: wanneer we een variabele declareren, kunnen we deze binnen dat blok van code gebruiken of binnen elke geneste blokken erin. Als we het buiten het bereik ervan proberen te gebruiken, wordt de waarde ingesteld op onbepaald.

Dit is de reden waarom we in onze laatste post de timer variabele buiten de functies die het gebruikten, omdat we die variabele nog steeds moesten pakken nadat de functies waren beëindigd.

 var timer; // Dit is een globale variabele functie run_right (stage, links) ... timer = setTimeout (function () run_right (2, left);, 200); ... function stop_running () document.getElementById ('j' ) .style.backgroundPosition = "0px 0px"; // Als 'timer' niet als globaal was ingesteld, konden we het hier niet stoppen clearTimeout (timer); 

Om de timer te wissen, hadden we dat nodig stop met rennen() om binnen de ruimte voor de variabele te vallen timer. Dus we hebben gemaakt timer een globale variabele die overal zou kunnen worden gebruikt, wat zou daar mis mee zijn?

Het probleem met globale variabelen

In elk gegeven bereik is het onmogelijk om twee items te hebben die hetzelfde worden genoemd. Als u zou proberen om twee verschillende variabelen met dezelfde naam te hebben, zou de browser er gewoon een van overschrijven. Dus als we een variabele hadden genaamd timer, en had een aparte variabele die ook werd genoemd timer dat werd binnen hetzelfde bereik genoemd, een ervan zou verwijderen en de andere vervangen, en we zouden onze code in de war hebben. Als we een hadden globale variabele riep timer, dan zou het interfereren met een andere variabele met de naam timer overal op de pagina - inclusief alle en alle gekoppelde JavaScript-bibliotheken en externe bestanden.

Dit is een enorme bron van hoofdpijn, je hebt zojuist een heel leuke JavaScript plug-in gezien, en je downloadt het naar je site, en plotseling crashen al je andere plug-ins ... Een van de plug-ins was slordig met wereldwijde variabelen, toevallig dezelfde naam delen met iets anders, je browser over zichzelf struikelt en de hele pagina tot stilstand komt.

Wat dit nog erger maakt, is dat je dit probleem nooit zult opmerken als je de code voor het eerst test. Net als onze animatiecode van de vorige post, zal deze prima werken op zichzelf. Maar hoe meer stukjes je toevoegt, hoe groter de kans dat je een naamgevingsconflict hebt, en je zult vastlopen door een tiental verschillende JavaScript-bestanden te doorzoeken om erachter te komen welke twee niet met elkaar kunnen opschieten..

Nu vraag je je misschien af: "Globale variabelen zijn zo handig! Wat als ik mijn code gewoon heel aandachtig bekijk en ervoor zorg dat ik geen conflicten heb?" Dat zou kunnen werken in een perfecte wereld, maar in werkelijkheid zul je vaak meerdere mensen aan het werk hebben op verschillende delen van dezelfde pagina, of terugkomen en jaren later verschillende delen van je code updaten, of zelfs code van derden hebben op uw pagina die u niet in de hand heeft (zoals betaalde advertenties).

Kortom, u zou geen globale variabelen meer willen hebben, dan dat u blootliggende bedrading langs de muren van uw huis of blootgestelde machines in uw auto zou willen hebben, het is gewoon een kwestie van tijd voordat er iets gebeurt dat het werk oppoetst. Gelukkig is er een betere manier om deze valkuilen te vermijden.

inkapseling

We kunnen alle voordelen van globale variabelen zonder de problemen hebben door een techniek genaamd te gebruiken inkapseling. Zie het alsof je een muur om je code bouwt met slechts een paar speciale deuren, niets kan in of uit die code komen tenzij je het specifiek toestaat.

JavaScript heeft een type variabele genaamd een voorwerp. Objecten zijn door de gebruiker gedefinieerde gegevensverzamelingen die informatie en functies bevatten (aangeduid als eigenschappen en methoden, respectievelijk). We gaan een functie schrijven die een speciaal object maakt dat alle functies bevat die we "gebakken" nodig hebben, en het zal ons zelfs toestaan ​​meer dan één robot te hebben zonder onze code te hoeven dupliceren!

We beginnen met het definiëren van een nieuwe functie met een variabelenaam. We moeten de variabele een paar argumenten doorgeven, ik geef het het HTML-element door dat we gaan animeren, plus enkele unieke waarden voor rijsnelheid en springhoogte, zodat we die kunnen variëren van robot tot robot.

 var RobotMaker = function (robot, run_speed, jump_height) // We zullen al onze functies en variabelen in dit gebied plaatsen. // Dit bevindt zich in onze 'ondoordringbare' muur, dus niets in dit // gebied zal conflicteren met andere code. return // Hierbinnen plaatsen we al onze 'deuren' ... // dit is de enige manier waarop alles // in of uit deze code kan komen. // En aangezien dit nog steeds binnen dezelfde 'scope' // ligt als RobotMaker, kunnen we alle hierboven genoemde variabelen gebruiken! 

Aangezien we al onze functies binnen onze nieuwe "muur" zullen plaatsen, zou het nu een goed moment zijn om opnieuw te bekijken welke bugs we hadden met de originele code. (Je kunt dat hier in actie zien)

Mogelijk merkt u dat als we op twee knoppen voor uitvoering (of een knop Rennen en springen) klikken zonder op de knop te klikken Hou op knop ertussen, J zal beide acties blijven doen. Een tweede probleem is dat het niet uitmaakt in welke richting J wordt geconfronteerd, wanneer we op de klikken Springen of Hou op knop, hij ziet elke keer gelijk terecht. Eindelijk, als u op klikt Springen opnieuw indrukken terwijl J uit een eerste sprong valt, hij zal door de pagina blijven vallen in een eindeloze lus.

Om deze zaken aan te pakken, moeten we specifieker zijn over wat we willen bereiken met elk van onze functies:

Wanneer we op Run Right klikken:

  1. Als J springt, doe dan niets en vervolg de sprong
  2. Als J naar links loopt, stop hem dan naar links
  3. Ren naar rechts en animeer naar het juiste frame
  4. Als J het einde van de etappe bereikt, stop dan met rennen en ga rechtop staan

Wanneer we op Run Left klikken:

  1. Als J springt, doe dan niets en vervolg de sprong
  2. Als J goed loopt, stop hem dan met rennen
  3. Ren naar links en animeer naar het juiste frame
  4. Als J het einde van de etappe bereikt, stop dan met rennen en ga naar links staan

Wanneer we klikken op Stoppen met lopen:

  1. Als J springt, doe dan niets en zet de sprong voort (we willen niet halverwege stoppen!)
  2. Als u naar links of rechts rent, stopt u met hardlopen
  3. Als u naar rechts kijkt, gaat u rechtop staan. Als je naar links kijkt, ga dan naar links staan

Wanneer we op Jump klikken:

  1. Als J springt, doe dan niets en zet de sprong voort (we willen niet opnieuw in de lucht springen!)
  2. Als J naar links of rechts draait, stop dan met hardlopen
  3. Start de sprong. Als J naar rechts kijkt, spring dan naar rechts. Als je naar links kijkt, spring dan naar links
  4. Land met uitzicht op dezelfde richting als de sprong

Allereerst gaan we nog een paar variabelen toevoegen. Aangezien de timer anders zou moeten werken voor rennen en springen, hebben we twee afzonderlijke timers. We willen ook een introduceren boolean (waar / niet waar) variabele om bij te houden of we naar links of rechts moeten kijken, en we zullen een maken stadium variabele alleen om ons te behoeden voor het uittypen van de volledige elementnaam.

 // Binnen de functie RobotMaker ... var stage = document.getElementById ('stage'); var run_timer, jump_timer; var face_right = true;

Daarna gaan we nog wat toevoegen aan onze functies voor hardlopen, naar links rennen en springen. Deze zullen grotendeels hetzelfde zijn, met een paar verschillen. Allereerst kunnen alle verwijzingen naar het element dat we animeren, worden vervangen door de variabele robot (die zal worden doorgegeven als een van de argumenten in de RobotMaker functie). Ten tweede hebben we een aantal kleine wijzigingen aangebracht in de rijsnelheid en springhoogte in de functies, zodat we die kunnen variëren door verschillende waarden door te geven. Ten derde gebruiken we de face_right variabel om te volgen met welke richting J wordt geconfronteerd (en in de springfunctie, met face_right om te beslissen welke springsprite te tonen). Ten slotte gebruiken we afzonderlijke timers voor rennen en springen.

 // Binnen de functie RobotMaker ... function run_r (phase, left) face_right = true; if ((left + (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 = top - (top * .1); robot.style.top = top + "px"; jump_timer = setTimeout (function () jmp (up, top);, 60);  else if (up) up = false; jump_timer = setTimeout (function () jmp (up, top);, 60);  else if (! 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;  

Al deze variabelen en functies bevinden zich in onze "muur", dus we moeten nu "deuren" maken om alleen toegang te hebben tot wat we nodig hebben. Deze vier "deuren" zullen object zijn methoden voor dezelfde vier functies die we eerder hadden en verwijzen naar de beschermde functies hierboven. We zullen ook onze bugfixatie voltooien door elke functie in te checken als de jump_timer gaat en zorgt er vervolgens voor dat de run_timer. Onthoud dat deze twee timers overal binnen de scope binnen bereik zijn RobotMaker () functie, zodat we ze hier kunnen gebruiken. Omdat het echter geen globale variabelen zijn, komen we elders geen problemen tegen.

 // Binnen de functie RobotMaker ... return run_right: function () if (! Jump_timer || jump_timer == undefined) clearTimeout (run_timer); run_r (1, robot.offsetLeft); , run_left: function () if (! jump_timer || jump_timer == undefined) clearTimeout (run_timer); run_l (1, robot.offsetLeft); , stop_running: function () if (! jump_timer || jump_timer == undefined) clearTimeout (run_timer); if (face_right) robot.style.backgroundPosition = "0px 0px";  else robot.style.backgroundPosition = "0px -50px"; , jump: function () if (! jump_timer || jump_timer == undefined) clearTimeout (run_timer); jmp (true, robot.offsetTop); 

Nu we een functie hebben geschreven die objecten maakt, kunnen we deze zo vaak gebruiken als we willen om objecten te maken die de gewenste animatie-eigenschappen hebben. Onder aan onze pagina zullen we twee nieuwe verklaren RobotMaker objecten en geef ze het element dat we willen animeren, een rijsnelheid en een springhoogte.

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

Nu hebben we geen gevaar voor iets in de RobotMaker () functie lekt uit en verstoort onze code, en we kunnen nog steeds de functies bereiken die we willen via de "deuren" die we als volgt hebben geïnstalleerd:

 

Dus nu kunt u het eindproduct zien op de Hyrgo Pen.

Merk op dat er geen problemen meer zijn met de functies die elkaar verstoren, en je kunt elke robot afzonderlijk bedienen zonder de andere te beïnvloeden. Inkapseling is een ongelooflijk belangrijke techniek en je moet er echt mee vertrouwd raken als je een interactief webontwerp wilt doen.

Als je wilt, kun je al deze code uitchecken, volledig reageren en je kunt de sprites downloaden met behulp van de volgende links: hier zijn de eerste sprites en hier zijn de tweede. Let op: om dezelfde code te laten werken met beide sprites, moest ik de tweede sprite maken in exact dezelfde indeling en afmetingen als de eerste.

Conclusie

Dus dat wraps deel drie van spriting! In onze volgende en laatste post zal ik die knoppen vervangen door onze robots de muis rond het scherm te laten volgen en je laten zien hoe je moet instellen gebeurtenis luisteraars en ondersteuning inschakelen voor browsers en aanraakapparaten.