Abschnitt 11 · Lektion 1
Text ändern
Abschnitt 10 hat gelesen, dieser Abschnitt schreibt. Und der häufigste Schreibzugriff überhaupt ist der einfachste: den Text eines Elements austauschen.
textContent ersetzt alles
<h1 id="titel">Alter Titel</h1>
<p id="satz">Ein Satz mit <strong>Betonung</strong> darin.</p>
<p id="zahl">Noch nichts</p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
h1 {
font-size: 1.5rem;
margin: 0 0 0.75rem;
}
p {
margin: 0 0 0.75rem;
} document.getElementById("titel").textContent = "Neuer Titel";
// Achtung: das ersetzt den ganzen Inhalt, auch das <strong>.
const satz = document.getElementById("satz");
satz.textContent = "Jetzt steht hier etwas anderes.";
console.log("noch ein strong da?", satz.querySelector("strong"));
// Was kein Text ist, wird zu Text gemacht.
document.getElementById("zahl").textContent = 42;
console.log("Typ im Element:", typeof document.getElementById("zahl").textContent); el.textContent = "Neuer Titel" setzt den Inhalt des Elements auf genau diesen Text. Das Wort ersetzt ist dabei wörtlich zu nehmen: der gesamte bisherige Inhalt ist danach weg, auch untergeordnete Elemente. Das <strong> im zweiten Beispielabsatz überlebt die Zuweisung nicht, und querySelector("strong") liefert danach null.
Was du zuweist, muss kein Text sein. Eine Zahl wird umgewandelt, aus 42 wird die Zeichenfolge "42". Das ist bequem und die häufigste Quelle für die Verwechslung, die in Abschnitt 12 wichtig wird: was im Element steht, ist immer Text, auch wenn du eine Zahl hineingeschrieben hast.
Es gibt keinen Weg, mit textContent versehentlich Markup zu erzeugen. Genau das ist seine Stärke.
Beides lässt sich nachfragen: satz.querySelector("strong") in der Eingabezeile der Console antwortet null, das Element ist weg. Und typeof document.getElementById("zahl").textContent antwortet "string", obwohl die Zeile im Beispiel eine Zahl zugewiesen hat.
innerHTML baut Elemente
<ul id="liste"></ul>
<p id="mit-text"></p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
} // innerHTML erwartet Markup und baut daraus wirklich Elemente.
document.getElementById("liste").innerHTML = `
<li>Erster</li>
<li>Zweiter</li>
`;
console.log("Einträge:", document.querySelectorAll("#liste li").length);
// Dieselbe Zeichenfolge mit textContent bleibt Text.
document.getElementById("mit-text").textContent = "<li>Erster</li>";
console.log("Elemente daraus:", document.querySelectorAll("#mit-text li").length); el.innerHTML = "<li>Erster</li>" liest die Zeichenfolge als Markup, baut daraus Elemente und hängt sie ein. Danach stehen dort wirklich zwei li, querySelectorAll findet sie, und sie verhalten sich wie jedes andere Element auf der Seite.
Dieselbe Zeichenfolge mit textContent gesetzt bleibt Text. Auf dem Bildschirm steht dann sichtbar <li>Erster</li>, und querySelectorAll findet nichts.
Der Unterschied ist also nicht die Schreibweise, sondern was der Browser mit der Zeichenfolge macht: ansehen oder ausführen.
Und das ist das Problem
<p id="sicher"></p>
<p id="unsicher"></p> body {
font-family: system-ui, sans-serif;
max-width: 40rem;
margin: 0;
padding: 1rem;
line-height: 1.6;
color: #1c1917;
}
p {
margin: 0 0 0.75rem;
} // Stell dir vor, das kommt aus einem Eingabefeld oder aus dem Netz.
const fremd = `<img src="kaputt.png" onerror="console.log('Hier läuft fremder Code')">`;
// Mit textContent steht der Text sichtbar da. Mehr passiert nicht.
document.getElementById("sicher").textContent = fremd;
// Mit innerHTML wird daraus ein echtes Element. Das Bild lädt nicht,
// also feuert onerror, und der fremde Code läuft. Schau in die Console.
document.getElementById("unsicher").innerHTML = fremd; Solange du das Markup selbst geschrieben hast, ist innerHTML harmlos. Sobald darin auch nur ein Stück Text steckt, das von jemand anderem kommt, ist es das nicht mehr.
Im dritten Beispiel steht in fremd ein Bild, das es nicht gibt. Ein Bild, das nicht lädt, löst sein onerror aus, und dort steht Code. Mit textContent gesetzt steht die Zeichenfolge sichtbar da und es passiert nichts. Mit innerHTML gesetzt wird sie zu einem Element, das Bild scheitert, und der fremde Code läuft. In der Console steht danach eine Zeile, die du nicht geschrieben hast.
Zwei Reiter zeigen zusammen, was da abgelaufen ist. Im Reiter Netzwerk steht kaputt.png als fehlgeschlagene Anfrage: Der Browser hat die Datei wirklich geholt, oder es zumindest versucht. Und in der Console steht das Ergebnis davon. Das Scheitern des Bildes war kein Unfall, sondern der Auslöser, und es funktioniert mit jedem Dateinamen, den es nicht gibt.
Diese Lücke hat einen Namen: Cross-Site-Scripting, kurz XSS. Fremder Text wird zu ausgeführtem Code, und der Code läuft mit allen Rechten deiner Seite. Er sieht, was der Nutzer sieht, und kann alles tun, was dein eigener Code tun könnte.
Ein verbreiteter Trugschluss dabei: „Ich filtere einfach <script> heraus.” Das reicht nicht. Über innerHTML eingesetzte <script>-Elemente laufen ohnehin nicht, der Angriff läuft über Attribute wie onerror, onload oder onclick, und davon gibt es Dutzende.
Die Regel für den Rest des Kurses
textContent ist der Standard. Wenn du Text setzen willst, nimm textContent. Immer.
innerHTML nur für Markup, das du selbst gebaut hast, und auch dann nur, wenn kein fremder Text darin steckt. Sobald ein Nutzername, eine Sucheingabe oder eine Antwort aus dem Netz mit hineinwandert, ist es der falsche Weg.
Und für den Fall „ich brauche wirklich neue Elemente” gibt es einen besseren: document.createElement in Lektion 11.5. Der ist etwas länger zu schreiben und kennt das Problem gar nicht erst, weil Text dort nie zu Markup werden kann.
Lektion 20.3 nimmt das Thema noch einmal auf und zeigt, wo fremder Text im Alltag überall herkommt. Die Regel steht schon hier, weil du ab dieser Lektion in der Lage bist, sie zu brechen.
Zum Mitnehmen
textContent ist der Standard. innerHTML nur für Markup, das du selbst gebaut hast, und niemals für Text, der von außen kommt.
Jetzt du
Basis Konto, kostenlosZu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.
Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.
Was in dieser Lektion steckt
-
Artikel mit 3 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.
-
Aufgabe im Editor, direkt im Browser geprüft
Öffnet sich mit dem Basis Konto.