14 KiB
Terrarium Projekt Del 3: DOM-manipulation og en Closure
Sketchnote af Tomomi Imura
Quiz før lektionen
Introduktion
Manipulation af DOM, eller "Document Object Model", er en central del af webudvikling. Ifølge MDN er "Document Object Model (DOM) den datarepræsentation af objekterne, der udgør strukturen og indholdet af et dokument på nettet." Udfordringerne ved DOM-manipulation på nettet har ofte været årsagen til, at man bruger JavaScript-frameworks i stedet for ren JavaScript til at håndtere DOM, men vi klarer os selv!
Derudover vil denne lektion introducere ideen om en JavaScript closure, som du kan tænke på som en funktion, der er indkapslet af en anden funktion, så den indre funktion har adgang til den ydre funktions scope.
JavaScript closures er et omfattende og komplekst emne. Denne lektion berører den mest grundlæggende idé, nemlig at du i terrariets kode vil finde en closure: en indre funktion og en ydre funktion konstrueret på en måde, der giver den indre funktion adgang til den ydre funktions scope. For meget mere information om, hvordan dette fungerer, kan du besøge den udførlige dokumentation.
Vi vil bruge en closure til at manipulere DOM.
Tænk på DOM som et træ, der repræsenterer alle de måder, et webside-dokument kan manipuleres på. Forskellige API'er (Application Program Interfaces) er blevet skrevet, så programmører, ved hjælp af deres foretrukne programmeringssprog, kan få adgang til DOM og redigere, ændre, omarrangere og på anden måde administrere det.
En repræsentation af DOM og HTML-markup, der refererer til det. Fra Olfa Nasraoui
I denne lektion vil vi færdiggøre vores interaktive terrarieprojekt ved at skabe den JavaScript, der gør det muligt for en bruger at manipulere planterne på siden.
Forudsætning
Du bør have HTML og CSS til dit terrarium klar. Ved slutningen af denne lektion vil du kunne flytte planterne ind og ud af terrariet ved at trække dem.
Opgave
I din terrarium-mappe skal du oprette en ny fil kaldet script.js
. Importér den fil i <head>
-sektionen:
<script src="./script.js" defer></script>
Bemærk: Brug
defer
, når du importerer en ekstern JavaScript-fil i HTML-filen, så JavaScript kun eksekveres, efter HTML-filen er fuldt indlæst. Du kunne også bruge attributtenasync
, som tillader scriptet at eksekvere, mens HTML-filen parses, men i vores tilfælde er det vigtigt, at HTML-elementerne er fuldt tilgængelige til at trække, før vi tillader drag-scriptet at blive eksekveret.
DOM-elementerne
Det første, du skal gøre, er at oprette referencer til de elementer, du vil manipulere i DOM. I vores tilfælde er det de 14 planter, der i øjeblikket venter i sidebjælkerne.
Opgave
dragElement(document.getElementById('plant1'));
dragElement(document.getElementById('plant2'));
dragElement(document.getElementById('plant3'));
dragElement(document.getElementById('plant4'));
dragElement(document.getElementById('plant5'));
dragElement(document.getElementById('plant6'));
dragElement(document.getElementById('plant7'));
dragElement(document.getElementById('plant8'));
dragElement(document.getElementById('plant9'));
dragElement(document.getElementById('plant10'));
dragElement(document.getElementById('plant11'));
dragElement(document.getElementById('plant12'));
dragElement(document.getElementById('plant13'));
dragElement(document.getElementById('plant14'));
Hvad sker der her? Du refererer til dokumentet og søger gennem dets DOM for at finde et element med et bestemt Id. Husk fra den første lektion om HTML, at du gav individuelle Id'er til hvert plantebillede (id="plant1"
)? Nu vil du gøre brug af den indsats. Efter at have identificeret hvert element, sender du det til en funktion kaldet dragElement
, som du vil bygge om lidt. Dermed bliver elementet i HTML nu drag-aktiveret, eller vil snart blive det.
✅ Hvorfor refererer vi til elementer via Id? Hvorfor ikke via deres CSS-klasse? Du kan henvise til den tidligere lektion om CSS for at besvare dette spørgsmål.
Closure
Nu er du klar til at oprette dragElement
-closure, som er en ydre funktion, der indkapsler en indre funktion eller funktioner (i vores tilfælde vil vi have tre).
Closures er nyttige, når en eller flere funktioner skal have adgang til en ydre funktions scope. Her er et eksempel:
function displayCandy(){
let candy = ['jellybeans'];
function addCandy(candyType) {
candy.push(candyType)
}
addCandy('gumdrops');
}
displayCandy();
console.log(candy)
I dette eksempel omgiver funktionen displayCandy
en funktion, der tilføjer en ny type slik til en array, der allerede eksisterer i funktionen. Hvis du kører denne kode, vil arrayet candy
være undefined, da det er en lokal variabel (lokal for closure).
✅ Hvordan kan du gøre arrayet candy
tilgængeligt? Prøv at flytte det uden for closure. På denne måde bliver arrayet globalt i stedet for kun at være tilgængeligt i closure's lokale scope.
Opgave
Under elementdeklarationerne i script.js
skal du oprette en funktion:
function dragElement(terrariumElement) {
//set 4 positions for positioning on the screen
let pos1 = 0,
pos2 = 0,
pos3 = 0,
pos4 = 0;
terrariumElement.onpointerdown = pointerDrag;
}
dragElement
får sit terrariumElement
-objekt fra deklarationerne øverst i scriptet. Derefter sætter du nogle lokale positioner til 0
for det objekt, der sendes ind i funktionen. Disse er de lokale variabler, der vil blive manipuleret for hvert element, når du tilføjer drag-and-drop-funktionalitet inden for closure til hvert element. Terrariet vil blive fyldt med disse trukne elementer, så applikationen skal holde styr på, hvor de placeres.
Derudover tildeles det terrariumElement
, der sendes til denne funktion, en pointerdown
-event, som er en del af web-API'er designet til at hjælpe med DOM-håndtering. onpointerdown
udløses, når en knap trykkes, eller i vores tilfælde, når et dragbart element berøres. Denne event handler fungerer på både web- og mobilbrowsere, med få undtagelser.
✅ Event handleren onclick
har meget mere støtte på tværs af browsere; hvorfor ville du ikke bruge den her? Tænk over den præcise type skærminteraktion, du prøver at skabe her.
Pointerdrag-funktionen
terrariumElement
er klar til at blive trukket rundt; når onpointerdown
-eventen udløses, kaldes funktionen pointerDrag
. Tilføj den funktion lige under denne linje: terrariumElement.onpointerdown = pointerDrag;
:
Opgave
function pointerDrag(e) {
e.preventDefault();
console.log(e);
pos3 = e.clientX;
pos4 = e.clientY;
}
Flere ting sker. Først forhindrer du de standardevents, der normalt sker ved pointerdown, i at finde sted ved at bruge e.preventDefault();
. På denne måde har du mere kontrol over grænsefladens opførsel.
Vend tilbage til denne linje, når du har bygget scriptfilen helt, og prøv det uden
e.preventDefault()
- hvad sker der?
For det andet skal du åbne index.html
i et browservindue og inspicere grænsefladen. Når du klikker på en plante, kan du se, hvordan 'e'-eventen fanges. Undersøg eventen for at se, hvor meget information der indsamles ved én pointerdown-event!
Dernæst bemærk, hvordan de lokale variabler pos3
og pos4
sættes til e.clientX. Du kan finde e
-værdierne i inspektionspanelet. Disse værdier fanger plantens x- og y-koordinater i det øjeblik, du klikker på eller berører den. Du vil have fin kontrol over planternes opførsel, når du klikker og trækker dem, så du holder styr på deres koordinater.
✅ Bliver det mere klart, hvorfor hele denne app er bygget med én stor closure? Hvis den ikke var det, hvordan ville du opretholde scope for hver af de 14 dragbare planter?
Fuldfør den indledende funktion ved at tilføje to mere pointer-event-manipulationer under pos4 = e.clientY
:
document.onpointermove = elementDrag;
document.onpointerup = stopElementDrag;
Nu angiver du, at du vil have planten til at blive trukket med pointeren, mens du bevæger den, og at trækbevægelserne skal stoppe, når du fravælger planten. onpointermove
og onpointerup
er alle dele af den samme API som onpointerdown
. Grænsefladen vil nu kaste fejl, da du endnu ikke har defineret funktionerne elementDrag
og stopElementDrag
, så byg dem ud næste gang.
Funktionerne elementDrag og stopElementDrag
Du vil fuldføre din closure ved at tilføje to flere interne funktioner, der vil håndtere, hvad der sker, når du trækker en plante og stopper med at trække den. Den opførsel, du ønsker, er, at du kan trække enhver plante til enhver tid og placere den hvor som helst på skærmen. Denne grænseflade er ret uopinioneret (der er f.eks. ingen dropzone) for at give dig mulighed for at designe dit terrarium præcis, som du vil, ved at tilføje, fjerne og omplacere planter.
Opgave
Tilføj funktionen elementDrag
lige efter den afsluttende krøllede parentes for pointerDrag
:
function elementDrag(e) {
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
console.log(pos1, pos2, pos3, pos4);
terrariumElement.style.top = terrariumElement.offsetTop - pos2 + 'px';
terrariumElement.style.left = terrariumElement.offsetLeft - pos1 + 'px';
}
I denne funktion redigerer du en masse af de oprindelige positioner 1-4, som du satte som lokale variabler i den ydre funktion. Hvad sker der her?
Mens du trækker, tildeler du pos1
ved at gøre det lig med pos3
(som du tidligere satte som e.clientX
) minus den aktuelle e.clientX
-værdi. Du udfører en lignende operation på pos2
. Derefter nulstiller du pos3
og pos4
til de nye X- og Y-koordinater for elementet. Du kan se disse ændringer i konsollen, mens du trækker. Derefter manipulerer du plantens CSS-stil for at sætte dens nye position baseret på de nye positioner af pos1
og pos2
, og beregner plantens top- og venstre X- og Y-koordinater baseret på sammenligning af dens offset med disse nye positioner.
offsetTop
ogoffsetLeft
er CSS-egenskaber, der sætter et elements position baseret på dets forælder; dets forælder kan være ethvert element, der ikke er positioneret somstatic
.
Alle disse genberegninger af positioner giver dig mulighed for at finjustere terrariets og planternes opførsel.
Opgave
Den sidste opgave for at fuldføre grænsefladen er at tilføje funktionen stopElementDrag
efter den afsluttende krøllede parentes for elementDrag
:
function stopElementDrag() {
document.onpointerup = null;
document.onpointermove = null;
}
Denne lille funktion nulstiller onpointerup
- og onpointermove
-events, så du enten kan genstarte plantens fremgang ved at begynde at trække den igen, eller begynde at trække en ny plante.
✅ Hvad sker der, hvis du ikke sætter disse events til null?
Nu har du fuldført dit projekt!
🥇Tillykke! Du har færdiggjort dit smukke terrarium.
🚀Udfordring
Tilføj en ny event handler til din closure for at gøre noget mere med planterne; for eksempel dobbeltklik på en plante for at bringe den foran. Vær kreativ!
Quiz efter lektionen
Gennemgang & Selvstudie
Selvom det virker trivielt at trække elementer rundt på skærmen, er der mange måder at gøre dette på og mange faldgruber, afhængigt af den effekt, du søger. Faktisk er der en hel drag and drop API, som du kan prøve. Vi brugte den ikke i dette modul, fordi den effekt, vi ønskede, var lidt anderledes, men prøv denne API på dit eget projekt og se, hvad du kan opnå.
Find mere information om pointer-events på W3C-dokumentation og på MDN web docs.
Tjek altid browserkapabiliteter ved hjælp af CanIUse.com.
Opgave
Ansvarsfraskrivelse:
Dette dokument er blevet oversat ved hjælp af AI-oversættelsestjenesten Co-op Translator. Selvom vi bestræber os på nøjagtighed, skal du være opmærksom på, at automatiserede oversættelser kan indeholde fejl eller unøjagtigheder. Det originale dokument på dets oprindelige sprog bør betragtes som den autoritative kilde. For kritisk information anbefales professionel menneskelig oversættelse. Vi påtager os intet ansvar for misforståelser eller fejltolkninger, der måtte opstå som følge af brugen af denne oversættelse.