Lekcja 10: Manipulacja DOM
Po wybraniu elementów DOM za pomocą metod poznanych w poprzedniej lekcji, możemy dynamicznie modyfikować ich zawartość, atrybuty i style za pomocą JavaScript. To pozwala na tworzenie interaktywnych i dynamicznych stron internetowych.
Modyfikowanie zawartości elementów
Istnieje kilka właściwości do odczytu i zmiany zawartości HTML i tekstowej elementów.
element.innerHTML
: Pobiera lub ustawia zawartość HTML wewnątrz elementu. Użycie tej właściwości do ustawienia zawartości pozwala na wstawienie nowego kodu HTML, który zostanie sparsowany przez przeglądarkę.element.textContent
: Pobiera lub ustawia zawartość tekstową elementu i wszystkich jego potomków. Ignoruje znaczniki HTML i zwraca tylko tekst. Jest to bezpieczniejszy i często szybszy sposób na manipulację samym tekstem.element.innerText
: Podobne dotextContent
, ale uwzględnia style CSS i nie zwraca tekstu z ukrytych elementów (np.display: none
). Jego działanie może być mniej przewidywalne i wolniejsze. Generalnie preferuje siętextContent
.
<div id="container">
<p>To jest <strong>pierwszy</strong> paragraf.</p>
</div>
// Plik HTML musi zawierać powyższy div
let container = document.getElementById("container");
let paragraph = container.querySelector("p");
// Odczyt zawartości
if (paragraph) {
console.log("innerHTML:", paragraph.innerHTML); // "To jest pierwszy paragraf."
console.log("textContent:", paragraph.textContent); // "To jest pierwszy paragraf."
console.log("innerText:", paragraph.innerText); // "To jest pierwszy paragraf."
// Przykład zmiany tylko tekstu wewnątrz strong
let strongElement = paragraph.querySelector("strong");
if (strongElement) {
strongElement.textContent = "zmodyfikowany";
}
console.log("Po zmianie textContent strong:", paragraph.innerHTML);
// Wynik: "To jest zmodyfikowany paragraf."
}
// Modyfikacja zawartości (przykłady zakomentowane)
// if (paragraph) {
// paragraph.innerHTML = "Nowa zawartość z <em>znacznikiem</em>.";
// // paragraph.textContent = "Nowa zawartość tekstowa (znaczniki zostaną potraktowane jak tekst).";
// }
Uwaga na bezpieczeństwo: Ustawianie innerHTML
na podstawie danych pochodzących od użytkownika bez odpowiedniego oczyszczenia może prowadzić do ataków Cross-Site Scripting (XSS). Jeśli wstawiasz tylko tekst, zawsze używaj textContent
.
Modyfikowanie atrybutów
Możemy odczytywać, ustawiać i usuwać atrybuty elementów HTML.
element.getAttribute(nazwaAtrybutu)
: Zwraca wartość podanego atrybutu.element.setAttribute(nazwaAtrybutu, wartosc)
: Ustawia wartość podanego atrybutu. Jeśli atrybut nie istnieje, zostanie utworzony.element.hasAttribute(nazwaAtrybutu)
: Zwracatrue
, jeśli element posiada podany atrybut, w przeciwnym raziefalse
.element.removeAttribute(nazwaAtrybutu)
: Usuwa podany atrybut z elementu.
Dla niektórych popularnych atrybutów (np. id
, src
, href
, class
, value
) istnieją również bezpośrednie właściwości obiektu elementu.
<a id="link" href="https://example.com" target="_blank">Przykładowy link</a>
<img id="obrazek" src="stary.jpg" alt="Opis obrazka">
// Plik HTML musi zawierać powyższe elementy
let link = document.getElementById("link");
let image = document.getElementById("obrazek");
if (link) {
// Odczyt atrybutów
console.log("href linku:", link.getAttribute("href")); // "https://example.com"
console.log("target linku:", link.target); // "_blank" (bezpośrednia właściwość)
// Modyfikacja atrybutów
link.setAttribute("href", "https://nowy-adres.pl");
link.textContent = "Nowy link";
// Sprawdzenie i usunięcie atrybutu
if (link.hasAttribute("target")) {
link.removeAttribute("target");
console.log("Usunięto atrybut target.");
}
// Dodanie nowego atrybutu
link.setAttribute("data-custom", "jakas-wartosc");
console.log("Nowy atrybut data-custom:", link.getAttribute("data-custom"));
}
if (image) {
console.log("src obrazka:", image.src); // Pełny URL do "stary.jpg"
// Modyfikacja atrybutów
image.src = "nowy.png"; // Użycie bezpośredniej właściwości
image.setAttribute("alt", "Nowy opis obrazka");
}
Modyfikowanie stylów CSS
Style CSS elementów można modyfikować na dwa główne sposoby:
1. Właściwość element.style
Pozwala na bezpośredni dostęp i modyfikację stylów inline elementu (tych zdefiniowanych w atrybucie style="..."
). Nazwy właściwości CSS są konwertowane na camelCase (np. background-color
staje się backgroundColor
).
<div id="box" style="width: 100px; height: 100px; border: 1px solid black;"></div>
// Plik HTML musi zawierać powyższy div
let box = document.getElementById("box");
if (box) {
// Odczyt stylu (tylko inline)
console.log("Szerokość boxa (inline):", box.style.width); // "100px"
// Modyfikacja stylów
box.style.backgroundColor = "lightblue";
box.style.borderWidth = "3px";
box.style.borderColor = "blue";
box.style.borderRadius = "10px";
// Usunięcie stylu (ustawienie na pusty string)
// box.style.borderRadius = "";
}
Ograniczenie: element.style
odczytuje tylko style ustawione bezpośrednio w atrybucie style
elementu, a nie te pochodzące z arkuszy CSS.
2. Manipulacja klasami CSS (element.classList
)
Jest to preferowany sposób na zmianę wyglądu elementów. Zamiast manipulować pojedynczymi stylami, dodajemy lub usuwamy predefiniowane klasy CSS.
element.classList.add(klasa1, klasa2, ...)
: Dodaje jedną lub więcej klas.element.classList.remove(klasa1, klasa2, ...)
: Usuwa jedną lub więcej klas.element.classList.toggle(klasa, [force])
: Dodaje klasę, jeśli jej nie ma, lub usuwa, jeśli jest. Opcjonalny drugi argument `force` (boolean) wymusza dodanie (`true`) lub usunięcie (`false`).element.classList.contains(klasa)
: Zwracatrue
, jeśli element ma podaną klasę, w przeciwnym raziefalse
.element.classList.replace(staraKlasa, nowaKlasa)
: Zastępuje istniejącą klasę nową.
/* W pliku CSS */
.highlight {
background-color: yellow;
font-weight: bold;
}
.error {
border: 2px solid red;
color: red;
}
.text-large { /* Przykładowa klasa */
font-size: 1.2em;
}
<p id="message">To jest ważna wiadomość.</p>
// Plik HTML musi zawierać powyższy paragraf
let message = document.getElementById("message");
if (message) {
// Dodanie klasy
message.classList.add("highlight");
// Sprawdzenie, czy klasa istnieje
console.log("Czy ma klasę highlight?", message.classList.contains("highlight")); // true
// Dodanie kolejnej klasy
message.classList.add("text-large");
// Usunięcie klasy
// message.classList.remove("text-large");
// Przełączenie klasy (toggle)
// Załóżmy, że chcemy włączać/wyłączać klasę 'error'
// message.classList.toggle("error"); // Jeśli nie ma 'error', doda ją. Jeśli jest, usunie.
// Zastąpienie klasy
// message.classList.replace("highlight", "normal-priority");
}
Tworzenie i dodawanie nowych elementów
document.createElement(tagName)
: Tworzy nowy węzeł elementu o podanej nazwie znacznika (np. 'p', 'div').document.createTextNode(text)
: Tworzy nowy węzeł tekstowy.parentNode.appendChild(newNode)
: DodajenewNode
jako ostatnie dzieckoparentNode
.parentNode.insertBefore(newNode, referenceNode)
: WstawianewNode
przedreferenceNode
(który musi być dzieckiemparentNode
).parentNode.removeChild(childNode)
: UsuwachildNode
zparentNode
.parentNode.replaceChild(newNode, oldNode)
: ZastępujeoldNode
przeznewNode
.
<ul id="lista">
<li>Element 1</li>
</ul>
// Plik HTML musi zawierać powyższą listę
let lista = document.getElementById("lista");
if (lista) {
// 1. Utwórz nowy element li
let nowyElement = document.createElement("li");
// 2. Utwórz tekst dla nowego elementu
let tekstElementu = document.createTextNode("Nowy Element 2");
// 3. Dodaj tekst do elementu li
nowyElement.appendChild(tekstElementu);
// 4. Dodaj nowy element li na koniec listy ul
lista.appendChild(nowyElement);
// Przykład wstawienia przed istniejącym elementem
let pierwszyElement = lista.querySelector("li"); // Znajdź pierwszy element li
let wstawianyElement = document.createElement("li");
wstawianyElement.textContent = "Element 0 (wstawiony)";
if (pierwszyElement) {
lista.insertBefore(wstawianyElement, pierwszyElement);
}
// Przykład usunięcia elementu
// let elementDoUsuniecia = lista.lastElementChild; // Ostatni element li
// if (elementDoUsuniecia) {
// lista.removeChild(elementDoUsuniecia);
// }
}
Zadanie praktyczne
Masz poniższy HTML. Napisz skrypt, który:
- Zmieni tekst wewnątrz elementu
h1
na "Zmieniony Nagłówek". - Doda klasę
active
do pierwszego elementu listy. - Zmieni atrybut
src
obrazka na "logo.png". - Doda nowy element
li
z tekstem "Element 4" na koniec listy.
<h1 id="title">Oryginalny Nagłówek</h1>
<ul id="items">
<li>Element 1</li>
<li>Element 2</li>
<li>Element 3</li>
</ul>
<img id="logo" src="old_logo.jpg" alt="logo">
Pokaż rozwiązanie
document.addEventListener("DOMContentLoaded", () => {
// 1. Zmień tekst h1
let title = document.getElementById("title");
if (title) {
title.textContent = "Zmieniony Nagłówek";
}
// 2. Dodaj klasę do pierwszego li
let itemsList = document.getElementById("items");
if (itemsList) {
let firstItem = itemsList.querySelector("li"); // lub itemsList.firstElementChild
if (firstItem) {
firstItem.classList.add("active");
}
// 4. Dodaj nowy element li
let newItem = document.createElement("li");
newItem.textContent = "Element 4";
itemsList.appendChild(newItem);
}
// 3. Zmień src obrazka
let logo = document.getElementById("logo");
if (logo) {
logo.setAttribute("src", "logo.png");
// lub logo.src = "logo.png";
}
});
Zadanie do samodzielnego wykonania
Stwórz przycisk HTML. Napisz skrypt, który po kliknięciu tego przycisku (na razie bez obsługi zdarzeń, po prostu wykonaj kod) stworzy nowy paragraf <p>
z tekstem "Nowy paragraf został dodany!" i doda go na końcu elementu body
dokumentu.
FAQ - Manipulacja DOM
Kiedy używać `innerHTML`, a kiedy `textContent`?
Używaj textContent
, gdy chcesz bezpiecznie wstawić lub odczytać tylko tekst, bez interpretacji HTML. Używaj innerHTML
, gdy świadomie chcesz wstawić strukturę HTML. Pamiętaj o ryzyku XSS przy innerHTML
, jeśli dane pochodzą z niezaufanego źródła.
Jaka jest różnica między `setAttribute` a bezpośrednim przypisaniem do właściwości (np. `img.src`)?
setAttribute
działa na poziomie atrybutów HTML. Bezpośrednie właściwości (jak src
, href
, id
, className
) są właściwościami obiektu DOM. W większości przypadków działają podobnie, ale mogą istnieć subtelne różnice (np. img.src
zwraca pełny URL, a getAttribute("src")
oryginalną wartość atrybutu).
Czy manipulacja DOM jest kosztowna wydajnościowo?
Tak, częste i rozległe modyfikacje DOM mogą być kosztowne, ponieważ przeglądarka musi przeliczyć układ strony (reflow) i ją przemalować (repaint). Staraj się minimalizować liczbę operacji na DOM, np. grupując zmiany lub modyfikując elementy poza głównym drzewem DOM przed ich wstawieniem.
Jak usunąć wszystkie dzieci elementu?
Najprostszym sposobem jest ustawienie innerHTML
na pusty string: element.innerHTML = ";
. Alternatywnie, można iterować w pętli, usuwając ostatnie dziecko, dopóki element nie będzie pusty: while (element.firstChild) { element.removeChild(element.firstChild); }
.
Co to jest DocumentFragment?
DocumentFragment
to lekki węzeł DOM, który działa jak tymczasowy kontener. Można do niego dodawać inne węzły (np. wiele elementów li
), a następnie dodać cały fragment do głównego DOM za pomocą jednej operacji appendChild
. Jest to wydajniejsze niż dodawanie wielu elementów pojedynczo.
Czy mogę klonować elementy DOM?
Tak, metoda element.cloneNode(deep)
tworzy kopię węzła. Jeśli argument deep
jest ustawiony na true
, kopiowane są również wszystkie dzieci węzła (rekurencyjnie). Jeśli false
(domyślnie), kopiowany jest tylko sam węzeł bez dzieci.