Anpassbare Textarea mit Syntaxhervorhebung für Code erstellen 

Avatar of Oliver Geer
Oliver Geer am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Als ich an einem Projekt arbeitete, das eine Editor-Komponente für Quellcode benötigte, wollte ich unbedingt eine Möglichkeit haben, die eingegebene Syntax im Editor hervorzuheben. Es gibt zwar Projekte dafür, wie z. B. CodeMirror, Ace und Monaco, aber das sind alles vollwertige, funktionsreiche Editoren, keine einfachen editierbaren Textareas mit Syntaxhervorhebung, wie ich es wollte.

Mit etwas Tüftelei gelang es mir, etwas zu entwickeln, das die Aufgabe erfüllt, und ich wollte teilen, wie ich es gemacht habe, da es die Integration einer beliebten Syntaxhervorhebungsbibliothek mit den Bearbeitungsfunktionen von HTML sowie einige interessante Randfälle zu berücksichtigen beinhaltet.

Live-Syntaxhervorhebung während des Tippens

Probieren Sie es gleich aus, während wir tiefer eintauchen!

Nach einem Vorschlag habe ich dies auch als benutzerdefiniertes Element auf GitHub veröffentlicht, sodass Sie die Komponente schnell als einzelnes <code-input>-Element auf einer Webseite verwenden können.

Das Problem

Zuerst versuchte ich, das Attribut contenteditable in einem div zu verwenden. Ich tippte etwas Quellcode in das div und ließ ihn über JavaScript bei oninput durch Prism.js, einen beliebten Syntax-Hervorheber, laufen. Klingt nach einer guten Idee, oder? Wir haben ein Element, das im Frontend bearbeitet werden kann, und Prism.js wendet seine Syntaxstile auf das im Element eingegebene an.

Chris erklärt in diesem Video, wie man Prism.js verwendet.

Aber das war ein No-Go. Jedes Mal, wenn sich der Inhalt des Elements ändert, wird das DOM manipuliert und der Cursor des Benutzers wird zum Anfang des Codes zurückgesetzt, was bedeutet, dass der Quellcode rückwärts angezeigt wird, mit den letzten Zeichen am Anfang und den ersten Zeichen am Ende.

White monospaced text on a black background that does not spell a coherent word.
Rückwärts angeordneter Code: nicht sehr nützlich

Als Nächstes versuchte ich es mit einer <textarea>, aber auch das funktionierte nicht, da Textareas nur einfachen Text enthalten können. Mit anderen Worten, wir können den eingegebenen Inhalt nicht gestalten. Eine textarea scheint der einzige Weg zu sein, den Text ohne unerwünschte Fehler zu bearbeiten – sie lässt Prism.js einfach nicht seine Arbeit tun.

Prism.js funktioniert wesentlich besser, wenn der Quellcode in einer typischen <pre><code>-Tag-Kombination eingeschlossen ist – es fehlt nur der bearbeitbare Teil der Gleichung.

Daher scheint keiner von beiden allein zu funktionieren. Aber ich dachte, *warum nicht beide?*

Die Lösung

Ich habe sowohl eine syntaxhervorgehobene <pre><code> *als auch* eine textarea zur Seite hinzugefügt und den innerText-Inhalt von <pre><code> per JavaScript über eine Funktion bei oninput ändern lassen. Ich habe auch ein aria-hidden-Attribut zum <pre><code>-Ergebnis hinzugefügt, damit Screenreader nur das eingeben, was in die <textarea> eingegeben wird, anstatt zweimal vorgelesen zu werden.

<textarea id="editing" oninput="update(this.value);"></textarea>

<pre id="highlighting" aria-hidden="true">
  <code class="language-html" id="highlighting-content"></code>
</pre>
function update(text) {
  let result_element = document.querySelector("#highlighting-content");
  // Update code
  result_element.innerText = text;
  // Syntax Highlight
  Prism.highlightElement(result_element);
}
The HTML for a link element pointed to CSS-Tricks is in black monospace on a white background above the same HTML link markup, but syntax-highlighted on a black background.
Jetzt ist es bearbeitbar und hervorgehoben!

Wenn die textarea bearbeitet wird – also eine Taste auf der Tastatur losgelassen wird –, ändert sich der syntaxhervorgehobene Code. Es gibt ein paar Fehler, zu denen wir noch kommen werden, aber ich möchte mich zuerst darauf konzentrieren, es so aussehen zu lassen, als würden Sie direkt das syntaxhervorgehobene Element bearbeiten, anstatt eine separate textarea.

Es soll sich wie ein Code-Editor anfühlen

Die Idee ist, die Elemente sichtbar zu verschmelzen, damit es so aussieht, als würden wir mit einem Element interagieren, obwohl tatsächlich zwei Elemente arbeiten. Wir können etwas CSS hinzufügen, das es der <textarea> und den <pre><code>-Elementen im Grunde erlaubt, konsistent in Größe und Abstand zu sein.

#editing, #highlighting {
  /* Both elements need the same text and space styling so they are directly on top of each other */
  margin: 10px;
  padding: 10px;
  border: 0;
  width: calc(100% - 32px);
  height: 150px;
}

#editing, #highlighting, #highlighting * {
  /* Also add text styles to highlighting tokens */
  font-size: 15pt;
  font-family: monospace;
  line-height: 20pt;
}

Dann wollen wir sie direkt übereinander positionieren

#editing, #highlighting {
  position: absolute;
  top: 0;
  left: 0;
}

Von dort aus ermöglicht z-index, dass die textarea vor dem hervorgehobenen Ergebnis gestapelt wird

/* Move the textarea in front of the result */
#editing {
  z-index: 1;
}

#highlighting {
  z-index: 0;
}

Wenn wir hier aufhören, sehen wir unseren ersten Fehler. Es sieht nicht so aus, als würde Prism.js die Syntax hervorheben, aber das liegt nur daran, dass die textarea das Ergebnis verdeckt.

Wohin ist die Hervorhebung verschwunden? (Hinweis: Sie versteckt sich im Hintergrund.)

Das können wir mit CSS beheben! Wir machen die <textarea> komplett transparent, *außer* dem Caret (Cursor)

/* Make textarea almost completely transparent */
#editing {
  color: transparent;
  background: transparent;
  caret-color: white; /* Or choose your favorite color */
}

Ah, viel besser!

HTML markup for a link element pointed to CSS tricks in a syntax-highlighted monospace font on a black background.
Wohin wird Sie dieser Link führen? Das entscheiden Sie.

Weitere Korrekturen!

Nachdem ich so weit gekommen war, spielte ich ein wenig mit dem Editor und konnte ein paar weitere Dinge finden, die behoben werden mussten. Das Gute ist, dass alle Probleme recht einfach mit JavaScript, CSS oder sogar HTML behoben werden können.

Native Rechtschreibprüfung entfernen

Wir erstellen einen Code-Editor, und Code hat viele Wörter und Attribute, die die native Rechtschreibprüfung eines Browsers als falsch geschriebene Wörter ansehen wird.

Showing the basic HTML markup for a link with syntax highlighting on a black background, where the href attribute is underlined in red.

Rechtschreibprüfung ist nichts Schlechtes; sie ist in dieser Situation nur nicht hilfreich. Wird etwas als falsch markiert, weil es falsch geschrieben ist, oder weil der Code ungültig ist? Das ist schwer zu sagen. Um das zu beheben, müssen wir nur das Attribut spellcheck auf der <textarea> auf false setzen.

<textarea id="editing" spellcheck="false" ...>

Behandlung von Zeilenumbrüchen

Es stellt sich heraus, dass innerText keine Zeilenumbrüche (\n) unterstützt.

White monospace text on a black background. The first line says "Hello, World" and the second line says "World" and is highlight in blue.
Obwohl "World!" auf einer neuen Zeile stehen sollte, zeigt der syntaxhervorgehobene Abschnitt es auf derselben Zeile an.

Die Funktion update muss bearbeitet werden. Anstatt innerText zu verwenden, können wir innerHTML verwenden, das offene Klammersymbol (<) durch &lt; und das Ampersand-Zeichen (&) durch &amp; ersetzen, um zu verhindern, dass HTML-Escapes ausgewertet werden. Dies verhindert, dass neue HTML-Tags erstellt werden, und ermöglicht die Anzeige des tatsächlichen Quellcodes anstelle des Versuchs des Browsers, den Code zu rendern.

result_element.innerHTML = text.replace(new RegExp("&", "g"), "&").replace(new RegExp("<", "g"), "<"); /* Global RegExp */
Showing the basic HTML document boilerplate with syntax highlighting on a black background. The body element contains the markup yo a paragraph that contains a link that says "CSS-Tricks is brilliant!"

Scrollen und Größenänderung

Hier ist noch eine Sache: Der hervorgehobene Code kann während des Bearbeitens nicht scrollen. Und wenn die Textarea gescrollt wird, scrollt der hervorgehobene Code nicht mit.

Zuerst stellen wir sicher, dass sowohl die textarea als auch das Ergebnis das Scrollen unterstützen

/* Can be scrolled */
#editing, #highlighting {
  overflow: auto;
  white-space: nowrap; /* Allows textarea to scroll horizontally */
}

Dann, um sicherzustellen, dass das Ergebnis *mit* der Textarea scrollt, aktualisieren wir das HTML und JavaScript wie folgt

<textarea id="editing" oninput="update(this.value); sync_scroll(this);" onscroll="sync_scroll(this);"></textarea>
function sync_scroll(element) {
  /* Scroll result to scroll coords of event - sync with textarea */
  let result_element = document.querySelector("#highlighting");
  // Get and set x and y
  result_element.scrollTop = element.scrollTop;
  result_element.scrollLeft = element.scrollLeft;
}

Einige Browser erlauben auch die Größenänderung einer textarea, aber das bedeutet, dass die textarea und das Ergebnis unterschiedliche Größen haben könnten. Kann CSS das beheben? Natürlich. Wir deaktivieren einfach die Größenänderung.

/* No resize on textarea */
#editing {
  resize: none;
}

Abschließende Zeilenumbrüche

Dank dieses Kommentars, der auf diesen Fehler hingewiesen hat.

Nun ist das Scrollen fast immer synchronisiert, aber es gibt immer noch einen Fall, in dem es immer noch nicht funktioniert. Wenn der Benutzer eine neue Zeile erstellt, befinden sich der Cursor und der Text der Textarea vorübergehend in der falschen Position, bevor auf der neuen Zeile Text eingegeben wird. Das liegt daran, dass der <pre><code>-Block aus ästhetischen Gründen eine leere letzte Zeile ignoriert. Da dies für eine funktionale Code-Eingabe und nicht für angezeigten Code bestimmt ist, muss die leere letzte Zeile angezeigt werden. Dies geschieht, indem der letzten Zeile Inhalt gegeben wird, damit sie nicht mehr leer ist, mit ein paar Zeilen JavaScript, die zur Funktion update hinzugefügt werden. Ich habe ein Leerzeichen verwendet, weil es für den Benutzer unsichtbar ist.

function update(text) {
  let result_element = document.querySelector("#highlighting-content");
  // Handle final newlines (see article)
  if(text[text.length-1] == "\n") { // If the last character is a newline character
    text += " "; // Add a placeholder space character to the final line 
  }
  // Update code
  result_element.innerHTML = text.replace(new RegExp("&", "g"), "&").replace(new RegExp("<", "g"), "<"); /* Global RegExp */
  // Syntax Highlight
  Prism.highlightElement(result_element);
}

Zeilen einrücken

Eines der kniffligeren Dinge, die angepasst werden müssen, ist die Handhabung von Zeileneinrückungen im Ergebnis. So wie der Editor derzeit eingerichtet ist, funktioniert das Einrücken von Zeilen mit Leerzeichen gut. Aber wenn Sie Tabs mehr als Leerzeichen bevorzugen, haben Sie vielleicht bemerkt, dass diese nicht wie erwartet funktionieren.

JavaScript kann verwendet werden, um die Tab-Taste richtig funktionieren zu lassen. Ich habe Kommentare hinzugefügt, um klarzustellen, was in der Funktion geschieht.

<textarea ... onkeydown="check_tab(this, event);"></textarea>
function check_tab(element, event) {
  let code = element.value;
  if(event.key == "Tab") {
    /* Tab key pressed */
    event.preventDefault(); // stop normal
    let before_tab = code.slice(0, element.selectionStart); // text before tab
    let after_tab = code.slice(element.selectionEnd, element.value.length); // text after tab
    let cursor_pos = element.selectionEnd + 1; // where cursor moves after tab - moving forward by 1 char to after tab
    element.value = before_tab + "\t" + after_tab; // add tab char
    // move cursor
    element.selectionStart = cursor_pos;
    element.selectionEnd = cursor_pos;
    update(element.value); // Update text to include indent
  }
}

Um sicherzustellen, dass die Tab-Zeichen die gleiche Größe haben, sowohl in der <textarea> als auch im syntaxhervorgehobenen Codeblock, bearbeiten Sie die CSS so, dass die tab-size-Eigenschaft enthalten ist.

#editing, #highlighting, #highlighting * {
  /* Also add text styles to highlighing tokens */
  [...]
  tab-size: 2;
}

Das Endergebnis

Nicht zu verrückt, oder? Wir haben nur <textarea>, <pre> und <code>-Elemente im HTML, ein paar Zeilen CSS, die sie stapeln, und eine Syntaxhervorhebungsbibliothek, um das Eingegebene zu formatieren. Und was ich daran am besten mag, ist, dass wir mit normalen, semantischen HTML-Elementen arbeiten, native Attribute nutzen, um das gewünschte Verhalten zu erzielen, CSS verwenden, um die Illusion zu erzeugen, dass wir nur mit einem Element interagieren, und dann zu JavaScript greifen, um einige Randfälle zu lösen.

Obwohl ich Prism.js für die Syntaxhervorhebung verwendet habe, wird diese Technik auch mit anderen funktionieren. Sie würde sogar mit einer selbst erstellten Syntaxhervorhebung funktionieren, wenn Sie das möchten. Ich hoffe, dies wird nützlich und kann an vielen Stellen eingesetzt werden, sei es ein WYSIWYG-Editor für ein CMS oder sogar Formulare, bei denen die Möglichkeit, Quellcode einzugeben, eine Anforderung ist, wie bei einer Front-End-Bewerbung oder vielleicht einer Quizfrage. Es ist schließlich eine <textarea>, daher kann sie in jedem Formular verwendet werden – Sie können sogar ein placeholder hinzufügen, wenn Sie möchten!