Lekcja 11: Zdarzenia (Events)
Zdarzenia to akcje lub wystąpienia, które dzieją się w systemie, z którym pracujesz – system informuje Cię o nich, abyś mógł na nie w jakiś sposób zareagować. W kontekście przeglądarki internetowej, zdarzenia to akcje wykonywane przez użytkownika (np. kliknięcie myszą, naciśnięcie klawisza) lub przez samą przeglądarkę (np. zakończenie ładowania strony).
Obsługa zdarzeń pozwala JavaScriptowi reagować na te akcje, czyniąc strony interaktywnymi.
Model obsługi zdarzeń
Istnieje kilka sposobów przypisywania obsługi zdarzeń do elementów HTML:
1. Atrybuty HTML `on*` (przestarzałe, niezalecane)
Można umieścić kod JavaScript bezpośrednio w atrybutach HTML, takich jak `onclick`, `onmouseover` itp.
<!-- NIE ZALECANE -->
<button onclick="alert("Kliknięto przycisk!"); console.log("Klik!");">Kliknij mnie</button>
Ten sposób miesza HTML z JavaScriptem, utrudnia zarządzanie kodem i jest uważany za złą praktykę.
2. Właściwości DOM `on*`
Można przypisać funkcję JavaScript do właściwości elementu DOM odpowiadającej zdarzeniu (np. `element.onclick`, `element.onmouseover`).
<button id="myButton">Kliknij mnie (DOM property)</button>
let myButton = document.getElementById("myButton");
if (myButton) {
// Przypisanie funkcji obsługi zdarzenia
myButton.onclick = function() {
console.log("Kliknięto przycisk (DOM property)!");
alert("Klik!");
};
// Można przypisać tylko JEDNĄ funkcję obsługi dla danego zdarzenia w ten sposób.
// Poniższe nadpisze poprzednią funkcję:
// myButton.onclick = function() { console.log("Inna obsługa kliknięcia"); };
}
Ten sposób jest lepszy niż atrybuty HTML, ale nadal ma ograniczenie do jednej funkcji obsługi na zdarzenie dla danego elementu.
3. Metoda `addEventListener()` (zalecana)
Jest to nowoczesny i najbardziej elastyczny sposób obsługi zdarzeń. Pozwala na dodanie wielu funkcji obsługi (listenerów) dla tego samego zdarzenia na jednym elemencie.
element.addEventListener(typZdarzenia, funkcjaObslugi, [opcjeLubUseCapture]);
typZdarzenia
: String określający typ zdarzenia (np. "click", "mouseover", "keydown") - bez prefiksu "on".funkcjaObslugi
: Funkcja, która zostanie wywołana, gdy zdarzenie wystąpi. Otrzymuje obiekt zdarzenia (Event object) jako argument.opcjeLubUseCapture
(opcjonalne): Obiekt z opcjami (np.{ once: true, capture: false }
) lub boolean określający fazę przechwytywania (capture phase - rzadziej używane, domyślniefalse
- faza bąbelkowania).
<button id="listenerButton">Kliknij mnie (Listener)</button>
let listenerButton = document.getElementById("listenerButton");
function handleClick1() {
console.log("Listener 1: Kliknięto przycisk!");
}
function handleClick2(event) {
console.log("Listener 2: Kliknięto przycisk!");
console.log("Typ zdarzenia:", event.type); // "click"
console.log("Element docelowy:", event.target); // Przycisk, który został kliknięty
// event.preventDefault(); // Można zapobiec domyślnej akcji (np. wysłaniu formularza)
// event.stopPropagation(); // Można zatrzymać propagację zdarzenia (bąbelkowanie)
}
if (listenerButton) {
// Dodanie pierwszego listenera
listenerButton.addEventListener("click", handleClick1);
// Dodanie drugiego listenera dla tego samego zdarzenia
listenerButton.addEventListener("click", handleClick2);
// Dodanie listenera, który wykona się tylko raz
listenerButton.addEventListener("mouseover", function() {
console.log("Najechano myszką (tylko raz)");
}, { once: true });
}
Usuwanie listenerów (`removeEventListener()`)
Aby usunąć listener dodany za pomocą `addEventListener`, musisz przekazać dokładnie tę samą funkcję, która została użyta przy dodawaniu.
// Aby móc usunąć listener, funkcja obsługi nie może być anonimowa
if (listenerButton) {
// Usunięcie pierwszego listenera
// listenerButton.removeEventListener("click", handleClick1);
}
Obiekt zdarzenia (Event Object)
Funkcja obsługi zdarzenia automatycznie otrzymuje jako argument obiekt zdarzenia (często nazywany event
, evt
lub e
). Zawiera on szczegółowe informacje o zdarzeniu.
Najważniejsze właściwości obiektu zdarzenia:
event.type
: Typ zdarzenia (np. "click").event.target
: Element HTML, na którym zdarzenie pierwotnie wystąpiło (najgłębszy element w drzewie DOM).event.currentTarget
: Element HTML, do którego aktualnie przypisany jest listener (ważne przy bąbelkowaniu).event.preventDefault()
: Metoda zapobiegająca domyślnej akcji przeglądarki związanej ze zdarzeniem (np. przejście do linku po kliknięciu<a>
, wysłanie formularza po kliknięciu `submit`).event.stopPropagation()
: Metoda zatrzymująca dalszą propagację (bąbelkowanie lub przechwytywanie) zdarzenia w drzewie DOM.- Specyficzne dla zdarzenia właściwości, np.:
event.key
,event.code
(dla zdarzeń klawiatury)event.clientX
,event.clientY
,event.pageX
,event.pageY
(dla zdarzeń myszy)event.target.value
(często używane przy zdarzeniach na polach formularzy)
Popularne typy zdarzeń
- Zdarzenia myszy:
click
: Kliknięcie lewym przyciskiem myszy.dblclick
: Podwójne kliknięcie.mousedown
: Wciśnięcie przycisku myszy.mouseup
: Puszczenie przycisku myszy.mouseover
: Najazd kursorem myszy na element.mouseout
: Zjazd kursorem myszy z elementu.mousemove
: Ruch myszy nad elementem.contextmenu
: Kliknięcie prawym przyciskiem myszy (wywołanie menu kontekstowego).
- Zdarzenia klawiatury:
keydown
: Wciśnięcie klawisza.keyup
: Puszczenie klawisza.keypress
(przestarzałe): Wciśnięcie klawisza generującego znak.
- Zdarzenia formularzy:
submit
: Wysłanie formularza (na elemencie<form>
).input
: Zmiana wartości w polu<input>
,<select>
,<textarea>
(nowoczesne, reaguje na każdą zmianę).change
: Zmiana wartości i utrata fokusu przez pole formularza.focus
: Uzyskanie fokusu przez element.blur
: Utrata fokusu przez element.
- Zdarzenia okna/dokumentu:
load
: Zakończenie ładowania zasobów strony (nawindow
).DOMContentLoaded
: Zakończenie budowy drzewa DOM przez przeglądarkę (nadocument
) - często używane do uruchamiania skryptów manipulujących DOM.resize
: Zmiana rozmiaru okna przeglądarki (nawindow
).scroll
: Przewijanie strony (nawindow
lub elemencie z przewijaniem).
Propagacja zdarzeń (Event Propagation)
Gdy zdarzenie wystąpi na elemencie, przechodzi przez dwie fazy propagacji w drzewie DOM:
- Faza przechwytywania (Capturing Phase): Zdarzenie "schodzi" w dół drzewa od
window
do elementu docelowego. Listenery dodane z opcjącapture: true
są wywoływane w tej fazie. - Faza bąbelkowania (Bubbling Phase): Zdarzenie "wędruje" w górę drzewa od elementu docelowego do
window
. Jest to domyślna faza, w której wywoływane są listenery (capture: false
lub brak opcji).
Można zatrzymać propagację za pomocą event.stopPropagation()
.
Delegacja zdarzeń (Event Delegation)
Jest to technika polegająca na dodaniu jednego listenera do wspólnego elementu nadrzędnego (np. listy ul
) zamiast dodawania wielu listenerów do każdego elementu podrzędnego (np. każdego li
). W funkcji obsługi sprawdzamy event.target
, aby określić, który element podrzędny wywołał zdarzenie.
Zalety delegacji:
- Mniej listenerów = lepsza wydajność.
- Automatyczna obsługa elementów dodanych dynamicznie do rodzica po przypisaniu listenera.
<ul id="parentList">
<li data-id="1">Element 1</li>
<li data-id="2">Element 2</li>
<li data-id="3">Element 3</li>
</ul>
<button id="addButton">Dodaj element</button>
let parentList = document.getElementById("parentList");
let addButton = document.getElementById("addButton");
let counter = 4;
if (parentList) {
parentList.addEventListener("click", function(event) {
// Sprawdź, czy kliknięto na element LI
if (event.target && event.target.nodeName === "LI") {
let itemId = event.target.getAttribute("data-id");
console.log("Kliknięto element listy o ID:", itemId);
event.target.style.textDecoration = "line-through"; // Przykład akcji
}
});
}
if (addButton) {
addButton.addEventListener("click", function() {
let newItem = document.createElement("li");
newItem.textContent = `Element ${counter}`;
newItem.setAttribute("data-id", counter);
if (parentList) {
parentList.appendChild(newItem);
}
counter++;
// Nowy element LI będzie automatycznie obsługiwany przez listener na UL!
});
}
Zadanie praktyczne
Stwórz przycisk HTML. Użyj `addEventListener`, aby po kliknięciu przycisku w konsoli pojawił się komunikat "Przycisk został kliknięty!", a tekst na przycisku zmienił się na "Kliknięto!".
Pokaż rozwiązanie
HTML:
<button id="interactiveButton">Kliknij mnie</button>
JavaScript:
document.addEventListener("DOMContentLoaded", () => {
let button = document.getElementById("interactiveButton");
if (button) {
button.addEventListener("click", function(event) {
console.log("Przycisk został kliknięty!");
// event.target odnosi się do przycisku
event.target.textContent = "Kliknięto!";
// Opcjonalnie: zapobiegnij dalszym kliknięciom lub zmień styl
// event.target.disabled = true;
});
}
});
Zadanie do samodzielnego wykonania
Stwórz pole tekstowe <input type="text" id="myInput">
oraz paragraf <p id="output"></p>
. Napisz skrypt, który będzie nasłuchiwał zdarzenia input
na polu tekstowym. Przy każdej zmianie wartości w polu tekstowym, tekst wpisany przez użytkownika powinien pojawić się wewnątrz paragrafu output
.
FAQ - Zdarzenia
Dlaczego `addEventListener` jest lepsze niż `onclick`?
`addEventListener` pozwala na dodanie wielu funkcji obsługi dla tego samego zdarzenia, oferuje większą kontrolę nad fazą propagacji (capture/bubble) i jest standardowym, nowoczesnym podejściem. Właściwości `on*` pozwalają tylko na jedną funkcję obsługi i są mniej elastyczne.
Co to jest `this` w funkcji obsługi zdarzenia?
W funkcji obsługi dodanej przez `addEventListener` (jeśli nie jest to funkcja strzałkowa), `this` zazwyczaj wskazuje na element, do którego listener jest przypisany (czyli `event.currentTarget`). W przypadku funkcji strzałkowych, `this` zachowuje wartość z kontekstu, w którym funkcja strzałkowa została zdefiniowana.
Kiedy używać `event.preventDefault()`?
Używaj `event.preventDefault()`, gdy chcesz anulować domyślną akcję przeglądarki dla danego zdarzenia. Typowe przypadki to zatrzymanie wysyłania formularza (przy walidacji po stronie klienta) lub zapobieganie przejściu do nowego URL po kliknięciu linku.
Kiedy używać `event.stopPropagation()`?
Używaj `event.stopPropagation()`, gdy chcesz zapobiec dalszemu "bąbelkowaniu" zdarzenia w górę drzewa DOM. Może to być przydatne, aby uniknąć wywołania listenerów na elementach nadrzędnych, ale należy używać tego ostrożnie, aby nie zakłócić oczekiwanego działania innych części aplikacji.
Jaka jest różnica między `event.target` a `event.currentTarget`?
`event.target` to element, który pierwotnie wywołał zdarzenie (np. konkretny element `li` wewnątrz `ul`). `event.currentTarget` to element, do którego aktualnie przypisany jest listener (np. element `ul`, jeśli używamy delegacji zdarzeń).
Czy mogę symulować zdarzenia w JavaScript?
Tak, można tworzyć i wysyłać syntetyczne zdarzenia za pomocą konstruktora `Event` (lub bardziej specyficznych, jak `MouseEvent`, `KeyboardEvent`) i metody `element.dispatchEvent(event)`. Jest to przydatne np. w testach automatycznych.