Lekcja 9: Wprowadzenie do DOM
DOM (Document Object Model) to kluczowa koncepcja w web developmencie, pozwalająca JavaScriptowi na interakcję ze strukturą, zawartością i stylami dokumentu HTML lub XML. DOM reprezentuje dokument jako drzewo obiektów, gdzie każdy element HTML, atrybut i fragment tekstu jest węzłem (node).
Co to jest DOM?
- Model Obiektowy Dokumentu: DOM to standard API (Application Programming Interface) definiowany przez W3C.
- Reprezentacja Drzewa: Przedstawia dokument HTML jako hierarchiczną strukturę drzewa węzłów.
- Interfejs Programistyczny: Umożliwia językom programowania (jak JavaScript) dynamiczny dostęp i modyfikację zawartości, struktury i stylu dokumentu.
Kiedy przeglądarka ładuje stronę HTML, tworzy w pamięci reprezentację DOM tego dokumentu. JavaScript może następnie używać tego modelu do manipulowania stroną.
Struktura Drzewa DOM
Każdy element w dokumencie HTML jest reprezentowany jako węzeł w drzewie DOM. Główne typy węzłów to:
- Węzeł dokumentu (Document Node): Korzeń drzewa, reprezentuje cały dokument (obiekt
document
w JavaScript). - Węzeł elementu (Element Node): Reprezentuje element HTML (np.
<p>
,<div>
,<h1>
). - Węzeł tekstowy (Text Node): Reprezentuje tekst zawarty w elemencie.
- Węzeł atrybutu (Attribute Node): Reprezentuje atrybut elementu (np.
href
w<a>
). - Węzeł komentarza (Comment Node): Reprezentuje komentarz HTML (
<!-- ... -->
).
Przykład prostego drzewa DOM dla poniższego HTML:
<!DOCTYPE html>
<html>
<head>
<title>Moja Strona</title>
</head>
<body>
<h1>Nagłówek</h1>
<p>To jest <strong>ważny</strong> tekst.</p>
</body>
</html>
Drzewo DOM (uproszczone):
Document └── html ├── head │ └── title │ └── Text: "Moja Strona" └── body ├── h1 │ └── Text: "Nagłówek" └── p ├── Text: "To jest " ├── strong │ └── Text: "ważny" └── Text: " tekst."
Dostęp do elementów DOM
JavaScript udostępnia obiekt document
, który jest punktem wejścia do DOM. Istnieje wiele metod pozwalających na znajdowanie i wybieranie elementów HTML.
Starsze metody (nadal używane)
document.getElementById(id)
: Zwraca element o podanym atrybucieid
(lubnull
, jeśli nie znaleziono). ID powinno być unikalne na stronie.document.getElementsByTagName(tagName)
: Zwraca kolekcję (HTMLCollection) wszystkich elementów o podanej nazwie znacznika (np. 'p', 'div').document.getElementsByClassName(className)
: Zwraca kolekcję (HTMLCollection) wszystkich elementów posiadających podaną klasę CSS.
<div id="main-container">
<p class="intro">Pierwszy paragraf.</p>
<p>Drugi paragraf.</p>
<p class="intro">Trzeci paragraf.</p>
</div>
// Plik HTML musi zawierać powyższy div
// Znajdź element po ID
let container = document.getElementById("main-container");
console.log(container);
// Znajdź wszystkie paragrafy
let allParagraphs = document.getElementsByTagName("p");
console.log(allParagraphs); // HTMLCollection [p.intro, p, p.intro]
console.log(allParagraphs.length); // 3
console.log(allParagraphs[0]); // Pierwszy paragraf
// Znajdź elementy po klasie
let introParagraphs = document.getElementsByClassName("intro");
console.log(introParagraphs); // HTMLCollection [p.intro, p.intro]
console.log(introParagraphs.length); // 2
Uwaga: HTMLCollection jest "żywa" - automatycznie aktualizuje się, gdy struktura DOM ulega zmianie.
Nowoczesne metody (zalecane)
Te metody wykorzystują selektory CSS do wybierania elementów, co jest bardziej elastyczne.
document.querySelector(selector)
: Zwraca pierwszy element pasujący do podanego selektora CSS (lubnull
).document.querySelectorAll(selector)
: Zwraca statyczną kolekcję (NodeList) wszystkich elementów pasujących do podanego selektora CSS.
// Plik HTML musi zawierać div#main-container z paragrafami
// Znajdź pierwszy element pasujący do selektora
let firstIntro = document.querySelector(".intro"); // Znajdzie pierwszy paragraf z klasą "intro"
console.log(firstIntro);
let mainDiv = document.querySelector("#main-container"); // Znajdzie div o id "main-container"
console.log(mainDiv);
let firstParagraphInMain = document.querySelector("#main-container p"); // Znajdzie pierwszy paragraf wewnątrz diva
console.log(firstParagraphInMain);
// Znajdź wszystkie elementy pasujące do selektora
let allIntroParagraphs = document.querySelectorAll(".intro");
console.log(allIntroParagraphs); // NodeList [p.intro, p.intro]
console.log(allIntroParagraphs.length); // 2
// NodeList można łatwo iterować np. za pomocą forEach
allIntroParagraphs.forEach(function(paragraph, index) {
console.log(`Paragraf intro ${index}:`, paragraph);
});
Uwaga: NodeList zwrócona przez querySelectorAll
jest statyczna - nie aktualizuje się automatycznie przy zmianach w DOM.
Nawigacja po drzewie DOM
Po wybraniu elementu (węzła) można nawigować po drzewie DOM, aby uzyskać dostęp do jego rodziców, dzieci lub rodzeństwa.
element.parentNode
: Zwraca węzeł rodzica.element.childNodes
: Zwraca NodeList wszystkich dzieci (w tym węzłów tekstowych i komentarzy).element.children
: Zwraca HTMLCollection tylko dzieci będących elementami HTML.element.firstChild
: Zwraca pierwszy węzeł-dziecko (może być tekstowy).element.lastChild
: Zwraca ostatni węzeł-dziecko.element.firstElementChild
: Zwraca pierwsze dziecko będące elementem HTML.element.lastElementChild
: Zwraca ostatnie dziecko będące elementem HTML.element.previousSibling
: Zwraca poprzedni węzeł-rodzeństwo.element.nextSibling
: Zwraca następny węzeł-rodzeństwo.element.previousElementSibling
: Zwraca poprzedni element-rodzeństwo.element.nextElementSibling
: Zwraca następny element-rodzeństwo.
// Plik HTML musi zawierać div#main-container z paragrafami
let mainContainer = document.getElementById("main-container");
let firstP = mainContainer.querySelector("p"); // Pierwszy paragraf
if (firstP) { // Sprawdzenie czy element został znaleziony
console.log("Rodzic pierwszego paragrafu:", firstP.parentNode); // div#main-container
console.log("Następny element po pierwszym paragrafie:", firstP.nextElementSibling); // Drugi paragraf
}
if (mainContainer) {
console.log("Dzieci kontenera (elementy):", mainContainer.children); // HTMLCollection [p.intro, p, p.intro]
}
Zadanie praktyczne
Stwórz prosty plik HTML z kilkoma elementami (np. nagłówek h1
, kilka paragrafów p
, lista ul
z elementami li
). Nadaj niektórym elementom ID i klasy. Następnie napisz skrypt JavaScript, który:
- Znajdzie element
h1
i wyświetli go w konsoli. - Znajdzie wszystkie elementy
li
i wyświetli ich liczbę. - Znajdzie element o określonym ID.
- Znajdzie wszystkie elementy o określonej klasie.
Pokaż rozwiązanie
Przykładowy HTML:
<!DOCTYPE html>
<html>
<head>
<title>Zadanie DOM</title>
</head>
<body>
<h1 id="main-heading">Witaj w DOM</h1>
<p class="content">To jest pierwszy paragraf.</p>
<p>To jest drugi paragraf.</p>
<ul class="item-list">
<li>Element 1</li>
<li class="special">Element 2 (specjalny)</li>
<li>Element 3</li>
</ul>
<script src="script.js"></script>
</body>
</html>
Przykładowy JavaScript (script.js):
// Upewnij się, że skrypt jest uruchamiany po załadowaniu DOM
document.addEventListener('DOMContentLoaded', (event) => {
// 1. Znajdź h1
let heading = document.querySelector("h1"); // lub document.getElementsByTagName("h1")[0];
console.log("Znaleziony nagłówek:", heading);
// 2. Znajdź wszystkie li i ich liczbę
let listItems = document.querySelectorAll("li"); // lub document.getElementsByTagName("li");
console.log("Liczba elementów li:", listItems.length);
// 3. Znajdź element po ID
let mainHeading = document.getElementById("main-heading");
console.log("Element o ID main-heading:", mainHeading);
// 4. Znajdź elementy po klasie
let contentParagraphs = document.getElementsByClassName("content");
console.log("Elementy o klasie content:", contentParagraphs);
let specialItems = document.querySelectorAll(".special"); // Użycie querySelectorAll dla klasy
console.log("Elementy o klasie special (querySelectorAll):", specialItems);
});
Zadanie do samodzielnego wykonania
Używając HTML z poprzedniego zadania, napisz skrypt, który znajdzie listę ul
o klasie item-list
, a następnie używając nawigacji po drzewie DOM (np. children
lub querySelectorAll
w kontekście znalezionego elementu), wyświetli w konsoli tekst każdego elementu li
znajdującego się wewnątrz tej listy.
FAQ - Wprowadzenie do DOM
Czy DOM to to samo co HTML?
Nie. HTML to język znaczników używany do strukturyzowania treści strony. DOM to obiektowa reprezentacja tego dokumentu HTML w pamięci przeglądarki, stworzona na podstawie kodu HTML. JavaScript interaguje z DOM, a nie bezpośrednio z plikiem HTML.
Które metody wybierania elementów są najlepsze?
Obecnie zaleca się używanie querySelector
i querySelectorAll
ze względu na ich elastyczność (korzystają z selektorów CSS) i spójność. getElementById
jest nadal bardzo szybki i użyteczny, gdy mamy unikalne ID. Metody getElementsByTagName
i getElementsByClassName
są starsze, ale wciąż działają.
Co to jest HTMLCollection i NodeList?
Są to kolekcje podobne do tablic, zwracane przez metody DOM. Główna różnica: HTMLCollection jest zazwyczaj "żywa" (automatycznie odzwierciedla zmiany w DOM), podczas gdy NodeList (zwracana przez querySelectorAll
) jest statyczna. Obie można iterować, ale NodeList ma wbudowaną metodę forEach
.
Dlaczego `childNodes` zawiera węzły tekstowe (np. białe znaki)?
childNodes
zwraca wszystkie typy węzłów-dzieci, w tym węzły tekstowe reprezentujące spacje, tabulacje czy znaki nowej linii między elementami HTML w kodzie źródłowym. Jeśli potrzebujesz tylko elementów HTML, użyj właściwości children
.
Czy mogę wybrać element, który jeszcze nie istnieje?
Nie. Metody wybierania elementów działają na drzewie DOM, które jest tworzone na podstawie załadowanego HTML. Jeśli spróbujesz wybrać element przed jego załadowaniem (np. skrypt w <head>
próbujący znaleźć element w <body>
), metoda zwróci null
lub pustą kolekcję.
Co oznacza, że NodeList jest "statyczna"?
Oznacza to, że kolekcja zwrócona przez querySelectorAll
jest "migawką" stanu DOM w momencie jej utworzenia. Jeśli po jej utworzeniu dodasz lub usuniesz elementy pasujące do selektora, statyczna NodeList nie zostanie automatycznie zaktualizowana. W przeciwieństwie do "żywych" HTMLCollection.