Eigenen WYSIWYG Editor programmieren – mit HTML, CSS & JavaScript

Eigenen WYSIWYG Editor programmieren – mit HTML, CSS & JavaScript Thumbnail
Veröffentlicht am 21. Juli 2019Zuletzt aktualisiert am 16. Juni 2020

Dich nerven fehlende oder unnötige Funktionen in WYSIWYG Editoren? Kein Problem! Hier zeige ich Dir, wie Du Dir Deinen eigenen WYSIWYG Editor mit HTML, CSS und JavaScript programmieren kannst.

Anzeige

WYSIWYG steht für „What you see is what you get“ und bedeutet zu Deutsch etwa „Was Du siehst, ist was du bekommst“. Damit sind Texteditoren gemeint, welche Direkt einen Text mit allen Formatierungen anzeigen und wir die Formatierungen beliebig ändern können. Sie werden auch häufig als Rich Text Editoren bezeichnet.

Viele der verfügbaren Editoren, wie TinyMCE funktionieren wirklich gut und sind für die meisten Projekte super einsetzbar. Allerdings findest Du den ein oder anderen Editor vielleicht etwas überladen, zu kompliziert oder Dich reizt es einfach, Deinen eigenen WYSIWYG Editor zu programmieren.

Die folgende Demo ist mit reinem HTML, CSS und purem JavaScript erstellt. In den nächsten Schritten gehe ich auf die Umsetzung dieses WYSIWYG Editors genau ein und am Ende wirst Du in der Lage sein, Deinen eigenen Editor zu programmieren – dafür benötigen wir nur etwa 10 Minuten!

Hier ist die laufende Demoversion des Editors, den wir jetzt zusammen coden wollen.

See the Pen WYSIWYG Editor by WebDEasy (@WebDEasy) on CodePen.

Anzeige

1. Das HTML-Gerüst entwerfen

Unsere Hauptaufgabe im HTML ist es, die Editor Toolbar zu erstellen. Dazu haben wir einen äußeren Container .editor. Dieser schließt einen Container für die Toolbar .toolbar und einen Container für die verschiedenen Ansichten (Visuelle Darstellung & HTML Ansicht) .content-area ein.

<div class="editor">
  <div class="toolbar">
  </div>
  <div class="content-area">
  </div>
</div>

1.1 Die Toolbar

Die Toolbar habe ich in zwei Zeilen angeordnet (.line), es können aber auch beliebig mehr werden. Außerdem gibt es in jeder Zeile mehrere Boxen .box für eine grobe Gliederung der Formatierungsmöglichkeiten.

In einer solchen Box befindet sich immer ein Span-Element mit einer Data Action. Diese Data Action beinhaltet den Befehl, der später auf dem markierten Text ausgeführt werden soll. So sehen die beiden Toolbar Zeilen im HTML aus:

<div class="line">
  
  <div class="box">
    <span class="btn icon smaller" data-action="bold" title="Bold">
      <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25432.svg">
    </span>
    <span class="btn icon smaller" data-action="italic" title="Italic">
      <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25392.svg">
    </span>
    <span class="btn icon smaller" data-action="underline" title="Underline">
      <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25433.svg">
    </span>
    <span class="btn icon smaller" data-action="strikeThrough" title="Strike through">
      <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25626.svg">
    </span>
  </div>
  
  <div class="box">
    <span class="btn icon has-submenu">
      <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25351.svg">
      <div class="submenu">
        <span class="btn icon" data-action="justifyLeft" title="Justify left">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25351.svg">  
        </span>
        <span class="btn icon" data-action="justifyCenter" title="Justify center">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25440.svg">  
        </span>
        <span class="btn icon" data-action="justifyRight" title="Justify right">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25288.svg">  
        </span>
        <span class="btn icon" data-action="formatBlock" title="Justify block">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25181.svg">  
        </span>
      </div>
    </span>
    <span class="btn icon" data-action="insertOrderedList" title="Insert ordered list">
      <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25242.svg">  
    </span>
    <span class="btn icon" data-action="insertUnorderedList" title="Insert unordered list">
      <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25648.svg">  
    </span>
    <span class="btn icon" data-action="outdent" title="Outdent">
      <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25410.svg">  
    </span>
    <span class="btn icon" data-action="indent" title="Indent">
      <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25233.svg">  
    </span>
  </div>

  <div class="box">
    <span class="btn icon" data-action="insertHorizontalRule" title="Insert horizontal rule">
      <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25232.svg">  
    </span>
  </div>
  
</div>

<div class="line">

  <div class="box">
    <span class="btn icon smaller" data-action="undo" title="Undo">
      <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25249.svg">
    </span>
    <span class="btn icon" data-action="removeFormat" title="Remove format">
      <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25454.svg">  
    </span>
  </div>
      
  <div class="box">
    <span class="btn icon smaller" data-action="createLink" title="Insert Link">
      <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25385.svg">
    </span>
    <span class="btn icon smaller" data-action="unlink" title="Unlink">
      <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25341.svg">
    </span>
  </div>
  
  <div class="box">
    <span class="btn icon" data-action="code" title="Show HTML-Code">
      <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25185.svg">
    </span>
  </div>
  
</div>

Die Data Action ist der Befehl, der später auf dem markierten Text ausgeführt wird. Dazu gibt es eine Liste der MDN web docs. Du kannst den Editor also ganz einfach um weitere Befehle hier erweitern.

1.2 Visuelle- und HTML-Ansicht

Im Inhaltsbereich haben wir zwei Bereiche: Eine HTML Ansicht und eine visuelle Ansicht. Dazu legen wir einen Container .visuell-view an, welcher zusätzlich die Eigenschaft contenteditable bekommt. Diese Eigenschaft sorgt dafür, dass wir Inhalt direkt Inline ohne Input bearbeiten können. Probier‘ das gern mal aus, falls Du diese Funktion nicht kennst.

<div class="visuell-view" contenteditable>
</div>

Außerdem fügen wir ein Textarea .html-view für die HTML-Ansicht ein, da wir später im Editor zwischen HTML- und visueller Ansicht wechseln möchten.

<textarea class="html-view"></textarea>

Und so sieht der gesamte HTML-Code im Überblick aus:

Anzeige
<div class="editor">
  <div class="toolbar">
    <div class="line">
      
      <div class="box">
        <span class="btn icon smaller" data-action="bold" title="Bold">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25432.svg">
        </span>
        <span class="btn icon smaller" data-action="italic" title="Italic">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25392.svg">
        </span>
        <span class="btn icon smaller" data-action="underline" title="Underline">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25433.svg">
        </span>
        <span class="btn icon smaller" data-action="strikeThrough" title="Strike through">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25626.svg">
        </span>
      </div>
      
      <div class="box">
        <span class="btn icon has-submenu">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25351.svg">
          <div class="submenu">
            <span class="btn icon" data-action="justifyLeft" title="Justify left">
              <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25351.svg">  
            </span>
            <span class="btn icon" data-action="justifyCenter" title="Justify center">
              <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25440.svg">  
            </span>
            <span class="btn icon" data-action="justifyRight" title="Justify right">
              <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25288.svg">  
            </span>
            <span class="btn icon" data-action="formatBlock" title="Justify block">
              <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25181.svg">  
            </span>
          </div>
        </span>
        <span class="btn icon" data-action="insertOrderedList" title="Insert ordered list">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25242.svg">  
        </span>
        <span class="btn icon" data-action="insertUnorderedList" title="Insert unordered list">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25648.svg">  
        </span>
        <span class="btn icon" data-action="outdent" title="Outdent">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25410.svg">  
        </span>
        <span class="btn icon" data-action="indent" title="Indent">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25233.svg">  
        </span>
        
      </div>
      <div class="box">
        <span class="btn icon" data-action="insertHorizontalRule" title="Insert horizontal rule">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25232.svg">  
        </span>
      </div>
      
    </div>
    <div class="line">
      
      <div class="box">
        <span class="btn icon smaller" data-action="undo" title="Undo">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25249.svg">
        </span>
        <span class="btn icon" data-action="removeFormat" title="Remove format">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25454.svg">  
        </span>
      </div>
      
      <div class="box">
        <span class="btn icon smaller" data-action="createLink" title="Insert Link">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25385.svg">
        </span>
        <span class="btn icon smaller" data-action="unlink" title="Unlink">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25341.svg">
        </span>
      </div>

      <div class="box">
        <span class="btn icon" data-action="code" title="Show HTML-Code">
          <img loading="lazy" src="https://image.flaticon.com/icons/svg/25/25185.svg">
        </span>
      </div>
      
    </div>
  </div>
  <div class="content-area">
    <div class="visuell-view" contenteditable>
    </div>
    <textarea class="html-view"></textarea>
  </div>
</div>

In meinem Editor verwende ich Icons von Flaticon. Deshalb muss ich einen entsprechenden Hinweis auf meiner Seite einfügen. Falls Du eigene Icons verwendest entfällt das für Dich natürlich.

<p>Icons made by <a href="https://www.flaticon.com/authors/dave-gandy" title="Dave Gandy">Dave Gandy</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a> is licensed by <a href="http://creativecommons.org/licenses/by/3.0/" title="Creative Commons BY 3.0" target="_blank">CC 3.0 BY</a></p>

2. WYSIWYG Editor stylen

Ich habe hier meinen SCSS Code in normales CSS umgewandelt, damit ihn jeder verstehen kann.

Hierzu erkläre ich aber nichts weiter, da CSS Grundlagen klar sein sollten, wenn man einen solchen Editor programmieren möchte. Natürlich kannst Du auch hier Deine eigenen Styles verwenden.

body {
  margin: 0;
  height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  font-family: 'Helvetica Neue', 'Helvetica', arial, sans-serif;
}
.editor {
  width: 40rem;
  min-height: 18rem;
  box-shadow: 0 0 4px 1px rgba(0, 0, 0, 0.3);
  border-top: 6px solid #4a4a4a;
  border-radius: 3px;
  margin: 2rem;
}
.editor .toolbar {
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
}
.editor .toolbar .line {
  display: flex;
  border-bottom: 1px solid #e2e2e2;
}
.editor .toolbar .line:last-child {
  border-bottom: none;
}
.editor .toolbar .line .box {
  display: flex;
  border-left: 1px solid #e2e2e2;
}
.editor .toolbar .line .box .btn {
  display: block;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  transition: 0.2s ease all;
}
.editor .toolbar .line .box .btn:hover, .editor .toolbar .line .box .btn.active {
  background-color: #e1e1e1;
  cursor: pointer;
}
.editor .toolbar .line .box .btn.icon img {
  width: 15px;
  padding: 10px;
}
.editor .toolbar .line .box .btn.icon.smaller img {
  width: 12px;
}
.editor .toolbar .line .box .btn.has-submenu {
  width: 20px;
  padding: 0 10px;
}
.editor .toolbar .line .box .btn.has-submenu::after {
  content: '';
  width: 6px;
  height: 6px;
  position: absolute;
  background-image: url(https://image.flaticon.com/icons/svg/25/25243.svg);
  background-repeat: no-repeat;
  background-size: cover;
  background-position: center;
  right: 4px;
}
.editor .toolbar .line .box .btn.has-submenu .submenu {
  display: none;
  position: absolute;
  top: 36px;
  left: -1px;
  z-index: 10;
  background-color: #fff;
  border: 1px solid #b5b5b5;
  border-top: none;
}
.editor .toolbar .line .box .btn.has-submenu .submenu .btn {
  width: 39px;
}
.editor .toolbar .line .box .btn.has-submenu:hover .submenu {
  display: block;
}
.editor .content-area {
  padding: 15px 12px;
  line-height: 1.5;
}
.editor .content-area .visuell-view {
  outline: none;
}
.editor .content-area .visuell-view p {
  margin: 12px 0;
}
.editor .content-area .html-view {
  outline: none;
  display: none;
  width: 100%;
  height: 200px;
  border: none;
  resize: none;
}

3. Funktionen in JavaScript programmieren

Im JavaScript müssen wir unsere Toolbar nun noch mit Funktionen belegen. Dazu deklarieren und initialisieren wir zuerst wichtige Elemente unseres Editors:

const editor = document.getElementsByClassName('editor')[0];
const toolbar = editor.getElementsByClassName('toolbar')[0];
const buttons = toolbar.querySelectorAll('.btn:not(.has-submenu)');

Um nicht jede Funktion einzeln zu programmieren haben wir im HTML bereits eine Data Action mit dem Befehl angelegt. Nun registrieren wir einfach den Klick auf diese Buttons in einer Schleife:

for(let i = 0; i < buttons.length; i++) {
  let button = buttons[i];
  
  button.addEventListener('click', function(e) {
  });
}

JavaScript stellt uns eine tolle Funktion document.execCommand() bereit. Diese ermöglicht es uns, unsere Action auf den markierten Text anzuwenden. Die Dokumentation zu dieser Funktion findest Du hier.

Wir lesen die Action aus der Data Action (im HTML) aus und führen den Befehl aus. Der zweite Parameter muss auf false stehen. Damit deaktivieren wir eine kleine UI, die z.B. in alten Internet Explorer Versionen angezeigt werden würde. Das brauchen wir aber nicht und Firefox oder Google Chrome unterstützen diese Funktionen sowieso nicht.

Anzeige
let action = this.dataset.action;
document.execCommand(action, false);

Damit ist unser Editor nun schon funktionsfähig. Die Funktion zum Umschalten zwischen der HTML Ansicht und der visuellen Ansicht und die Eingabe eines Links erwarten allerdings noch mehr von uns. Deshalb bauen wir ein switch-case Statement ein.

switch(action) {
  case 'code':
    execCodeAction(this, editor);
    break;
  case 'createLink':
    execLinkAction();
    break;
  default:
    execDefaultAction(action);
}

Für „normale“ Funktionen verwenden wir die execDefaultAction(action) Funktion. Dort wird lediglich die execCommand() Funktion von JavaScript mit der Data Action des jeweiligen Buttons ausgeführt.

function execDefaultAction(action) {
  document.execCommand(action, false);
}

Wenn wir zwischen der HTML Ansicht und der visuellen Ansicht umschalten wollen, blenden wir jeweils die andere ein und tauschen die Inhalte.

function execCodeAction(button, editor) {
  const contentArea = editor.getElementsByClassName('content-area')[0];
  const visuellView = contentArea.getElementsByClassName('visuell-view')[0];
  const htmlView = contentArea.getElementsByClassName('html-view')[0];

  if(button.classList.contains('active')) { // show visuell view
    visuellView.innerHTML = htmlView.value;
    htmlView.style.display = 'none';
    visuellView.style.display = 'block';

    button.classList.remove('active');     
  } else {  // show html view
    htmlView.innerText = visuellView.innerHTML;
    visuellView.style.display = 'none';
    htmlView.style.display = 'block';

    button.classList.add('active'); 
  }
}

Als nächstes wollen wir noch einen Link einfügen können. Dazu müssen wir natürlich angeben, welchen. Das machen wir über die JavaScript prompt() Funktion.

function execLinkAction() {
  let linkValue = prompt('Link (e.g. https://webdeasy.de/)');
  document.execCommand('createLink', false, linkValue);
}

Damit haben wir alle Funktionen hinzugefügt. Und hier noch einmal der vollständige JavaScript Code:

const editor = document.getElementsByClassName('editor')[0];
const toolbar = editor.getElementsByClassName('toolbar')[0];
const buttons = toolbar.querySelectorAll('.btn:not(.has-submenu)');

for(let i = 0; i < buttons.length; i++) {
  let button = buttons[i];
  
  button.addEventListener('click', function(e) {
    let action = this.dataset.action;
    
    switch(action) {
      case 'code':
        execCodeAction(this, editor);
        break;
      case 'createLink':
        execLinkAction();
        break;
      default:
        execDefaultAction(action);
    }
    
  });
}

function execCodeAction(button, editor) {
  const contentArea = editor.getElementsByClassName('content-area')[0];
  const visuellView = contentArea.getElementsByClassName('visuell-view')[0];
  const htmlView = contentArea.getElementsByClassName('html-view')[0];

  if(button.classList.contains('active')) { // show visuell view
    visuellView.innerHTML = htmlView.value;
    htmlView.style.display = 'none';
    visuellView.style.display = 'block';

    button.classList.remove('active');     
  } else {  // show html view
    htmlView.innerText = visuellView.innerHTML;
    visuellView.style.display = 'none';
    htmlView.style.display = 'block';

    button.classList.add('active'); 
  }
}

function execLinkAction() {
  let linkValue = prompt('Link (e.g. https://webdeasy.de/)');
  document.execCommand('createLink', false, linkValue);
}

function execDefaultAction(action) {
  document.execCommand(action, false);
}

4. Fazit

Wie Du nun siehst, kannst Du relativ schnell einen eigenen WYSIWYG Editor programmieren und nach Deinen Vorstellungen stylen und programmieren. Wenn Dir dieser Beitrag gefallen hat, würde ich mich freuen, wenn Du meinen Blog durch Deinen wiederholten Besuch unterstützt. 🙂

Ähnliche Beiträge
NEW 🚀
Beteilige dich an der Unterhaltung

8 Kommentare

  1. Hallo. Super Anleitung. Hat mir super geholfen. Ich bekomme es leider nicht hin, einen Button für Bilder einzufügen. Ich bin leider nicht Fit in Java-Script.
    Gruss Andre

    1. Hi, freut mich!
      Das Einfügen von Bildern funktioniert genau wie die Links. Der zweite Parameter muss die Bild-URL sein. Du kannst versuchen, trotz wenig Ahnung von JavaScript das ganze 1:1 von den Links zu übernehmen. Statt createLink musst Du insertImage verwenden.
      LG LH

  2. Hallo,

    in unserer Firma gibt es Seiten mit „Wiki-Tags“ Wie kann ich die bearbeiten ohne immer komplett in den html-code zu gehen.
    Beispiel: [addresslist|aktivekunden|Kunden]
    Angezeigt wird Kunde, bei Click wir in einem neuen Fenster die aktive Kundenliste angezeigt.
    ich möchte nun per Editor den Befehl und die Beschriftung ändern.

    1. Hallo,
      so ohne weitere Hintergrundinformationen kann ich das nicht beantworten. Den Editor einzubauen ist eine Sache, das was bei Ihnen vermutlich relevanter ist, ist die Speicherung der Daten. Dort müssen vermutlich eher Anpassungen gemacht werden, so wie es sich für mich anhört.

      LH

  3. Moin,
    sehr cooles Teil. Vielen Dank.
    Ich bastele damit jetzt schon eine Weile rum und scheitere an zwei dingen:
    1. Die Übergabe des HMTL-Codes aus dem Editor an bekomme ich nicht hin. Hast du da einen Tipp?
    2. Wie bekommt man da eine Link-Funktion rein. Auch das überfordert mich leider.

    Vielen Dank und lieben Gruß
    Anna

    1. Hi!
      1. Wohin möchtest Du den Code übergeben?
      2. Für den Link benötigst Du noch eine zusätzliche Eingabe, nämlich den Link den Du einfügen möchtest. Hier ist die Doku von execCommand(), schau hier mal nach dem Punkt „createLink“: https://developer.mozilla.org/de/docs/Web/API/Document/execCommand

      Bei execCommand() musst Du dabei noch den dritten Parameter übergeben. Ich würde also eine extra Abfrage (genau wie bei action == ‚code‘) machen und dann ein Popup oder Ähnliches öffnen, den einzufügenden Link abfragen und zusätzlich an die execCommand()-Funktion übergeben.
      Im Internet findest Du dazu viele Beispiele 🙂

      Viele Grüße
      LH

      1. Moin vielen Dank für die Antwort.
        Beim „an übergeben“ ist mit doch das Ziel abhanden gekommen.
        Ich würde gerne den vom Editor genierten HTML-Code via POST als Formular an eine PHP-Script übergeben.

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.