Przedostatnim elementem naszej podróży jest DOM (Document Object Model), czyli obiektowy model dokumentu HTML. Gdy przeglądarka ładuje stronę internetową, tworzy z reprezentacji HTML drzewo obiektów zwanych węzłami (nodes). Każdy element HTML staje się węzłem typu element, teksty stają się węzłami tekstowymi, itd. Obiekt document
w JavaScript daje nam dostęp do tego drzewa DOM, dzięki czemu możemy dynamicznie odczytywać i modyfikować zawartość strony, strukturę elementów oraz reagować na zdarzenia (np. kliknięcia, ruch myszką, wpisywanie tekstu).
Innymi słowy, DOM jest interfejsem między JavaScriptem a HTML. Pozwala on traktować zawartość strony jako strukturę danych, którą możemy zmieniać „w locie” po załadowaniu strony, tworząc interaktywne aplikacje. Ta sekcja wprowadzi metody manipulacji DOM: jak wybierać elementy, zmieniać ich treść i styl, dodawać nowe elementy lub usuwać istniejące, a także jak obsługiwać zdarzenia użytkownika.
Wybieranie elementów DOM
Aby cokolwiek zrobić z DOM, najpierw musimy znaleźć (wybrać) elementy HTML, które nas interesują. JavaScript oferuje kilka metod do wyszukiwania elementów w dokumencie:
document.getElementById("id")
– zwraca element o danym id. Identyfikator powinien być unikalny w ramach strony. Jeśli element o takim id nie istnieje, zwrócinull
.
Przykład:const header = document.getElementById("main-header");
pobierze element o identyfikatorze"main-header"
.document.querySelector("selector")
– zwraca pierwszy element pasujący do danego selektora CSS. Selektorem może być nazwa tagu (np."p"
), klasa (".classname"
), id ("#id"
) lub bardziej złożony selektor jak w CSS. Jeśli żaden element nie pasuje, zwracanull
.
Przykład:const pierwszyLink = document.querySelector("a");
zwróci pierwszy napotkany element<a>
na stronie.const specjalny = document.querySelector(".active")
zwróci pierwszy element z klasą „active”.document.querySelectorAll("selector")
– zwraca wszystkie elementy pasujące do selektora, w postaci kolekcji (NodeList). NodeList można iterować (np. pętlą for…of) lub przekształcić na prawdziwą tablicę. Jeśli żaden element nie pasuje, zwraca pustą kolekcję.
Przykład:const wszystkieLinki = document.querySelectorAll("a");
pobierze listę wszystkich linków<a>
w dokumencie.- Inne metody: Starsze, wyspecjalizowane metody to m.in.
getElementsByClassName("nazwaKlasy")
,getElementsByTagName("nazwaTagu")
, które zwracają kolekcje elementów danej klasy lub tagu. Działają poprawnie, ale obecniequerySelectorAll
jest często preferowane ze względu na elastyczność (można nim zastąpić tamte metody jednym uniwersalnym wywołaniem z odpowiednim selektorem CSS, np.document.querySelectorAll("div.note")
zamiastgetElementsByClassName("note")
).
Przykład wykorzystujący różne metody selekcji:
// HTML przykładowo zawiera <ul id="lista"><li class="item">A</li><li class="item">B</li></ul>
const lista = document.getElementById("lista"); // <ul> z id "lista"
const pierwszyItem = document.querySelector("#lista .item"); // pierwszy element z klasą "item" wewnątrz #lista
const wszystkieItemy = document.querySelectorAll(".item"); // wszystkie elementy z klasą "item"
console.log(lista.tagName); // "UL"
console.log(wszystkieItemy.length); // np. 2 (jeśli są dwa <li class="item">)
Kiedy mamy już referencję do elementu/elementów DOM, możemy przejść do manipulowania nimi.
Manipulacja treścią i stylami
Najprostszą formą manipulacji jest zmiana tekstowej treści lub HTML wewnątrz elementu, a także modyfikacja stylów CSS elementu.
- Zmiana zawartości tekstowej: Właściwość
textContent
pozwala odczytać lub ustawić tekst wewnątrz elementu (bez interpretacji HTML). Podobnie działainnerText
(który dodatkowo uwzględnia pewne aspekty widoczności CSS), ale zazwyczajtextContent
jest preferowane do czystego tekstu.
Jeśli chcemy wstawić kawałek HTML (np. tag <strong>), można użyćinnerHTML
, który traktuje przypisany string jako kod HTML do wyrenderowania wewnątrz elementu. - Zmiana stylów (CSS): Każdy element posiada właściwość
style
, która jest obiektem reprezentującym style inline (wbudowane w elemencie). Możemy ustawiać poszczególne właściwości CSS jako atrybuty tego obiektu. Np.element.style.color = "red";
ustawi kolor tekstu na czerwony, aelement.style.backgroundColor = "yellow";
tło na żółte. Właściwości w stylu camelCase odpowiadają tym w CSS (np.background-color
w CSS tobackgroundColor
w JavaScript).
Innym podejściem jest manipulacja klasami CSS elementu, co jest często bardziej praktyczne – zamiast bezpośrednio zmieniać wiele stylów w skrypcie, dodajemy lub usuwamy klasy, które mają zdefiniowane style w arkuszu CSS. Do tego służy właściwośćclassList
, która udostępnia metody takie jakadd
,remove
,toggle
do zarządzania listą klas elementu.
Przykłady:
const naglowek = document.getElementById("main-header");
naglowek.textContent = "Witaj w JavaScript!"; // zmiana tekstu nagłówka
naglowek.style.color = "blue"; // zmiana koloru tekstu naglowek.style.fontSize = "24px"; // zmiana rozmiaru czcionki (np. na 24px)
naglowek.classList.add("highlight"); // dodanie klasy CSS do elementu
// (zakładamy, że w CSS mamy np. .highlight { background-color: yellow; } )
Powyższy kod zmienia tekst nagłówka (np. <h1 id="main-header">
) na „Witaj w JavaScript!”, ustawia jego tekst na niebieski, rozmiar czcionki na 24px, a następnie dodaje mu klasę "highlight"
, która może np. podświetlać tło. Manipulacja textContent
usuwa ewentualną poprzednią zawartość i wstawia nowy tekst. Gdybyśmy zamiast tego użyli innerHTML = "<span>Witaj</span>"
, to nagłówek zawierałby nowy element <span>
wewnątrz. Trzeba być ostrożnym z innerHTML
– jeśli wstawiamy w ten sposób dane pochodzące od użytkownika, może to prowadzić do problemów z bezpieczeństwem (wstrzyknięcie niechcianego kodu HTML/JS).
- Zmiana atrybutów: Poza tekstem i stylem, elementy HTML mają różne atrybuty (np.
src
dla<img>
,href
dla<a>
,value
dla<input>
itd.). Możemy je zmieniać poprzez bezpośrednie właściwości (np.image.src = "nowy.png"
) lub metodysetAttribute
/getAttribute
. Np.link.setAttribute("target", "_blank")
doda atrybut target do linku.
Tworzenie i usuwanie elementów w DOM
JavaScript może nie tylko zmieniać istniejące elementy, ale też tworzyć nowe elementy od zera i dodawać je do DOM, lub usuwać elementy.
- Tworzenie elementu: Służy do tego metoda
document.createElement("nazwaTagu")
. Tworzy ona nowy węzeł elementu w oderwaniu od dokumentu (na razie nie jest widoczny na stronie, dopiero po dołączeniu). Po utworzeniu możemy ustawić jego zawartość lub atrybuty:
const newParagraph = document.createElement("p"); // utworzenie elementu <p>
newParagraph.textContent = "To jest nowy akapit.";
newParagraph.style.fontWeight = "bold";
Powyżej utworzyliśmy nowy paragraf z tekstem i ustawiliśmy pogrubienie tekstu stylem. Nadal nie jest on dodany do strony.
- Dodawanie do DOM: Aby element pojawił się na stronie, musimy go wstawić do drzewa DOM, np. jako dziecko jakiegoś innego elementu. Najczęściej używamy
parentElement.appendChild(nowyElement)
lub nowszej metodyparentElement.append(nowyElement)
:
const sekcja = document.getElementById("section1");
sekcja.appendChild(newParagraph);
Załóżmy, że w HTML mamy element o id section1
(np. <div id="section1"></div>
). Powyższy kod dołącza nasz nowy paragraf jako dziecko tego elementu (na koniec zawartości sekcji). Metoda appendChild
umieszcza element zawsze jako ostatni element wewnątrz rodzica. Jeśli chcemy wstawić w konkretne miejsce, można użyć parentElement.insertBefore(newElem, referencyjnyElem)
lub metod typu after
, before
(np. element.before(newElem)
wstawia nowyElem przed danym elementem w DOM).
- Usuwanie elementu: Możemy usunąć element z DOM na kilka sposobów. Mając referencję do elementu, możemy wywołać
element.remove()
, co spowoduje jego usunięcie z drzewa DOM. Ta metoda jest prostsza, wspierana w większości nowoczesnych przeglądarek. Alternatywnie, możemy wywołaćparent.removeChild(element)
, co zadziała nawet w starszych środowiskach: javascriptKopiujEdytujnewParagraph.remove(); // usuwa newParagraph z DOM (jego rodzicem był sekcja)
Po tej operacjinewParagraph
istnieje dalej jako obiekt w pamięci, ale nie jest już częścią dokumentu widocznego dla użytkownika. Jeśli nie będziemy już go używać, po pewnym czasie zostanie oczyszczony z pamięci przez GC.
Podsumowując, dzięki createElement
, appendChild/append
i removeChild/remove
możemy dowolnie manipulować strukturą strony: dynamicznie dodawać nowe sekcje, listy, elementy interfejsu lub usuwać te, które są niepotrzebne.
Obsługa zdarzeń
Strona internetowa staje się interaktywna głównie dzięki zdarzeniom (events) i ich obsłudze w JavaScript. Zdarzenie to np. kliknięcie w przycisk, najechanie myszą na element, naciśnięcie klawisza, załadowanie strony itp. W JavaScript możemy podpiąć funkcje (zwane obsługami zdarzeń lub handlerami) pod konkretne zdarzenia na konkretnych elementach. Gdy zdarzenie wystąpi, przeglądarka wywoła naszą funkcję, która zareaguje w określony sposób.
Najlepszym sposobem obsługi zdarzeń jest użycie metody addEventListener
. Składnia: element.addEventListener(typZdarzenia, funkcjaDoWywołania)
.
Przykład: obsługa kliknięcia (event "click"
) na przycisku:
const button = document.querySelector("button#clickMe");
button.addEventListener("click", () => {
alert("Kliknięto przycisk!");
});
W powyższym kodzie zakładamy, że w HTML istnieje <button id="clickMe">Kliknij mnie</button>
. Skrypt pobiera ten przycisk i rejestruje nasłuch na zdarzenie click
. Gdy użytkownik kliknie ten przycisk, wykona się funkcja strzałkowa, która wywoła alert
z komunikatem. Zamiast funkcji strzałkowej moglibyśmy przekazać nazwę zdefiniowanej gdzieś indziej funkcji, np. button.addEventListener("click", pokazKomunikat)
, jeśli funkcja pokazKomunikat
była zdefiniowana.
Obsługa zdarzeń może również modyfikować DOM dynamicznie. Na przykład, możemy sprawić, że kliknięcie przycisku doda nowy element do strony lub zmieni styl istniejącego elementu:
const dodajBtn = document.getElementById("dodajAkapit"); // np. <button id="dodajAkapit">Dodaj akapit</button>
dodajBtn.addEventListener("click", () => {
const p = document.createElement("p");
p.textContent = "Nowy akapit dodany przez JavaScript.";
document.body.appendChild(p);
});
W tym przykładzie po kliknięciu przycisku o id "dodajAkapit"
tworzymy nowy paragraf i dodajemy go na sam dół strony (document.body
). Za każdym kliknięciem będzie dodany kolejny nowy akapit. Widać tu połączenie kilku poznanych wcześniej rzeczy: selekcja elementu, obsługa zdarzenia, tworzenie elementu i modyfikacja DOM.
Kilka wskazówek dot. obsługi zdarzeń:
- Istnieje wiele typów zdarzeń:
click
,dblclick
,mouseover
,mouseout
,mousedown
,mouseup
,keydown
,keyup
,submit
,change
,input
i wiele innych. Każde zdarzenie przekazuje do funkcji obsługującej obiekt zdarzenia (event object) zawierający szczegółowe informacje (np. który klawisz został naciśnięty, jaka pozycja kursora, itp.). W naszym arrow function nie użyliśmy parametru, ale moglibyśmy go dodać, np.(event) => { ... }
i korzystać zevent
. - Można podpiąć wiele niezależnych „nasłuchiwaczy” do tego samego zdarzenia na tym samym elemencie za pomocą
addEventListener
(każdy wykona się w kolejności dodania). To przewaga nad starszą metodą ustawianiaelement.onclick = ...
, gdzie przypisanie nowej funkcji nadpisuje poprzednią. - Aby usunąć obsługę zdarzenia, istnieje odpowiednia para
removeEventListener
, ale wymaga to referencji do tej samej funkcji, którą dodano (przydatne np. przy czyszczeniu nasłuchów, gdy element jest usuwany).
Dzięki zdarzeniom i manipulacji DOM możemy tworzyć bogate interakcje: walidować formularze na bieżąco, tworzyć galerie obrazów, reagować na ruch myszki, budować gry przeglądarkowe, i wiele więcej.