I denna laboration ska vi titta närmare på hur händelser (events) fungerar i webbläsaren. Vi kommer att utgå från ett enkelt “måltavla”-spel där vi ska klicka på olika färgade fält för att samla poäng.
0 - Förberedelser
Skapa en ny mapp lab-4b och ladda ner startmaterialet (lab4b.zip). Packa upp filerna i mappen. Du ska ha index.html och style.css. Skapa även en tom fil script.js och länka in den i index.html (glöm inte type="module").
1 - Event-objektet
När en händelse inträffar (t.ex. ett klick) skapar webbläsaren ett event-objekt som innehåller information om händelsen. Detta objekt skickas med som argument till din händelsehanterare (callback-funktionen).
- Hämta elementet med id
boardoch spara i en variabel. - Lägg till en klick-lyssnare på
board. - I lyssnarfunktionen, ta emot event-objektet (ofta kallat
eellerevent) och logga det till konsolen.
Förslag på lösning
const board = document.querySelector("#board");
board.addEventListener("click", function(event) {
console.log(event);
});
- Klicka på måltavlan och inspektera objektet i konsolen. Titta särskilt på egenskaperna
targetochcurrentTarget.target: Elementet som faktiskt klickades på (t.ex. den gula cirkeln).currentTarget: Elementet som lyssnaren sitter på (i detta fall#board). Observera att om du loggar helaevent-objektet och sedan inspekterar det i konsolen, kancurrentTargetibland visanulleftersom objektet loggas som en referens och dess egenskaper kan ändras över tid. För att se det korrekta värdet, loggaevent.currentTargetdirekt.
Testa att logga event.target och event.currentTarget specifikt och klicka på olika färger.
2 - Bubbling (Händelsespridning)
Händelser i DOM:en “bubblar” uppåt. Det betyder att om du klickar på den innersta cirkeln (svart), så triggas först klick-händelsen på den svarta cirkeln, sedan på den gula, sedan röda, blåa, och till sist på #board och body osv.
Vi ska testa detta genom att lägga lyssnare på alla cirklar.
- Börja med att kommentera bort koden du skrev ovan.
- Hämta alla element med klassen
target(cirklarna har både klassentargetoch en färg-klass). - Loopa igenom dem och lägg till en klick-lyssnare på varje.
- I lyssnaren, logga exempelvis
event.currentTarget.classNameså att du ser vilken färg cirkeln har.
Förslag på lösning
const targets = document.querySelectorAll(".target");
for (const circle of targets) {
circle.addEventListener("click", function(event) {
console.log("Klickade på en cirkel:", event.currentTarget.className);
});
}
- Klicka nu på den svarta cirkeln (mitten). Hur många loggar får du? Varför? Du borde se loggar för alla cirklar som ligger “under” muspekaren, från den innersta och utåt. Detta är bubbling.
3 - Räkna poäng
Om vi nu skulle vilja ge poäng baserat på vilken färg man träffar så får vi vara lite försiktiga. Säg att vi vill ge följande poäng:
- Svart: 100 poäng
- Gul: 50 poäng
- Röd: 20 poäng
- Blå: 10 poäng
Börja med att på elementen i html-dokumentet lägga in data-score-attribut med rätt poäng på varje cirkel.
Som vi sett ovan så kommer ett klick på en cirkel att triggera händelsen på alla färger som ligger “under” muspekaren, från den innersta och utåt. Detta kommer nog innebära problem för vår poängräkning. Men vi provar och ser vad som händer.
I script.js, gör följande:
- Högst upp, skapa en global variabel
scoresom börjar på 0. - I klick-lyssnaren för cirklarna (som du skapade i steg 2):
- läs ut
data-score-attributet från den cirkel som klickades och konvertera det till ett nummer. - Logga poängen till konsolen.
- Öka
scoremed rätt antal poäng. - Uppdatera texten i elementet
#scoremed den nya poängen.
- läs ut
Försök till lösning
let score = 0;
const scoreDisplay = document.querySelector("#score");
const targets = document.querySelectorAll(".target");
for (const circle of targets) {
circle.addEventListener("click", function(event) {
const points = Number(event.target.dataset.score);
console.log("Träff!", points);
score += points;
scoreDisplay.textContent = score;
});
}
Oavsett om du valde event.target eller event.currentTarget att läsa ut poängen ifrån så kommer du att få en alldeles för hög summa. Detta beror på att händelsen bubblar uppåt och triggar alla lyssnare på vägen.
För att förhindra detta måste vi stoppa händelsen från att bubbla vidare.
- Uppdatera din klick-lyssnare genom att lägga till
event.stopPropagation()som det första du gör i funktionen.
Förslag på lösning (med stoppad bubbling)
let score = 0;
const scoreDisplay = document.querySelector("#score");
const targets = document.querySelectorAll(".target");
for (const circle of targets) {
circle.addEventListener("click", function(event) {
event.stopPropagation();
const points = Number(event.target.dataset.score);
console.log("Träff!", points);
score += points;
scoreDisplay.textContent = score;
});
}
Testa spelet! Nu ska du bara få poäng för den specifika ring du klickar på.
3.5 - Alternativ lösning: “Event delegation”
Det finns ett annat, ofta bättre sätt att lösa detta på. Istället för att lägga lyssnare på varje enskilt element och stoppa bubblingen, kan vi utnyttja bubblingen till vår fördel. Vi kan lägga en enda lyssnare på behållaren (#board) och sedan kontrollera var klicket faktiskt skedde. Detta kallas för “Event delegation”.
- Kommentera bort din gamla kod i
script.jsoch skriv en ny lösning som:- Bara har en enda
addEventListenerpå#board. - I lyssnaren, använd
event.targetför att se vad som klickades. - Kontrollera om det klickade elementet har ett
data-scoreattribut (använddataset.score). - Om det har det, öka poängen och uppdatera texten.
- Bara har en enda
Förslag på lösning
let score = 0;
const scoreDisplay = document.querySelector("#score");
const board = document.querySelector("#board");
board.addEventListener("click", function (event) {
const points = Number(event.target.dataset.score);
if (points) {
score += points;
scoreDisplay.textContent = score;
console.log("Träff!", points);
}
});
Ser du hur mycket renare koden blev? Vi behöver inte loopa över element, och vi behöver inte bry oss om stopPropagation. Eftersom event.target alltid pekar på det innersta elementet som klickades, så får vi automatiskt rätt poäng.
4 - Utforskning
Nu är det dags att experimentera med andra typer av händelser.
Här är en lista på några händelser som kan vara intressanta att testa:
- Mus-händelser:
click,dblclick,mousedown,mouseup,mousemove - Hover-händelser:
mouseenterochmouseleave - Tangentbords-händelser:
keydown,keyup
Börja med att göra en funktion som du kan använda för att testa när de olika händelserna sker.
- I
script.js, skapa en funktionlogEventsom tar en händelse som argument och loggar händelsen typ till konsolen.
Förslag på lösning
function logEvent(event) {
console.log(event.type);
}
Nu kan du lägga en lyssnare på #board för varje händelse och använd logEvent som callback. På så sätt kan du se i konsolen när varje händelse sker och försöka bekanta dig med de olika händelserna.
Fundera: Är det några events som du inte kan få att fungera? Varför tror du att det är så?
- Prova att lägga
keyupochkeydownlyssnarna pådocumentistället för#board. Blir det någon skillnad?
- När du känner bekantat dig med när de olika händelserna inträffar lägger du till en rad i
logEventsom logga ut helaevent-objektet.console.log(event); - Experimentera runt lite till på sidan och titta på olika egenskaper i
event-objektet för de olika händelserna.- Vilka egenskaper finns för mus-händelser jämfört med tangentbords-händelser?
- Vilka egenskaper ändras nör du flyttar runt musen?
- Vilka ändras när du trycker på olika tangenter?
Tangentbordsstyrning
Gör så att man kan återställa poängen till 0 genom att trycka på tangenten ‘R’.
Förslag: Börja med att utnyttja logEvent som du redan har kopplad för se vad som finns i event-objektet när du trycker på tangenten. Använd sedan den informationen i en ny lyssnare, specifik för keydown, för att kolla om det var ‘R’ som trycktes ned och nollställ poängen i så fall.
Tips
- Tangentbordshändelser hamnar oftast på
document(eller specifika input-fält). - Lyssna på
keydownpådocument. - Kolla
event.keyför att se vilken tangent som trycktes ned.
Förslag på lösning
document.addEventListener("keydown", function(event) {
if (event.key === "r" || event.key === "R") {
score = 0;
scoreDisplay.textContent = score;
console.log("Poängen nollställdes!");
}
});
Prova dig fram och se vad mer du kan göra!