Eigenen WYSIWYG Editor programmieren – in 10 Minuten!

Eigenen WYSIWYG Editor programmieren – in 10 Minuten!

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

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. Diese werden uns dann in Echtzeit angezeigt.

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 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!

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

1. HTML-Gerüst

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 src="https://image.flaticon.com/icons/svg/25/25432.svg">
    </span>
    <span class="btn icon smaller" data-action="italic" title="Italic">
      <img src="https://image.flaticon.com/icons/svg/25/25392.svg">
    </span>
    <span class="btn icon smaller" data-action="underline" title="Underline">
      <img src="https://image.flaticon.com/icons/svg/25/25433.svg">
    </span>
    <span class="btn icon smaller" data-action="strikeThrough" title="Strike through">
      <img src="https://image.flaticon.com/icons/svg/25/25626.svg">
    </span>
  </div>
  
  <div class="box">
    <span class="btn icon has-submenu">
      <img src="https://image.flaticon.com/icons/svg/25/25351.svg">
      <div class="submenu">
        <span class="btn icon" data-action="justifyLeft" title="Justify left">
          <img src="https://image.flaticon.com/icons/svg/25/25351.svg">  
        </span>
        <span class="btn icon" data-action="justifyCenter" title="Justify center">
          <img src="https://image.flaticon.com/icons/svg/25/25440.svg">  
        </span>
        <span class="btn icon" data-action="justifyRight" title="Justify right">
          <img src="https://image.flaticon.com/icons/svg/25/25288.svg">  
        </span>
        <span class="btn icon" data-action="formatBlock" title="Justify block">
          <img 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 src="https://image.flaticon.com/icons/svg/25/25242.svg">  
    </span>
    <span class="btn icon" data-action="insertUnorderedList" title="Insert unordered list">
      <img src="https://image.flaticon.com/icons/svg/25/25648.svg">  
    </span>
    <span class="btn icon" data-action="outdent" title="Outdent">
      <img src="https://image.flaticon.com/icons/svg/25/25410.svg">  
    </span>
    <span class="btn icon" data-action="indent" title="Indent">
      <img 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 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 src="https://image.flaticon.com/icons/svg/25/25249.svg">
    </span>
    <span class="btn icon" data-action="removeFormat" title="Remove format">
      <img src="https://image.flaticon.com/icons/svg/25/25454.svg">  
    </span>
  </div>
  
  <div class="box">
    <span class="btn icon" data-action="code" title="Show HTML-Code">
      <img 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 hier. Du kannst den Editor also ganz einfach um weitere Befehle hier erweitern.

1.2 Ansichten (Inhalt)

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:

<div class="editor">
  <div class="toolbar">
    <div class="line">
      
      <div class="box">
        <span class="btn icon smaller" data-action="bold" title="Bold">
          <img src="https://image.flaticon.com/icons/svg/25/25432.svg">
        </span>
        <span class="btn icon smaller" data-action="italic" title="Italic">
          <img src="https://image.flaticon.com/icons/svg/25/25392.svg">
        </span>
        <span class="btn icon smaller" data-action="underline" title="Underline">
          <img src="https://image.flaticon.com/icons/svg/25/25433.svg">
        </span>
        <span class="btn icon smaller" data-action="strikeThrough" title="Strike through">
          <img src="https://image.flaticon.com/icons/svg/25/25626.svg">
        </span>
      </div>
      
      <div class="box">
        <span class="btn icon has-submenu">
          <img src="https://image.flaticon.com/icons/svg/25/25351.svg">
          <div class="submenu">
            <span class="btn icon" data-action="justifyLeft" title="Justify left">
              <img src="https://image.flaticon.com/icons/svg/25/25351.svg">  
            </span>
            <span class="btn icon" data-action="justifyCenter" title="Justify center">
              <img src="https://image.flaticon.com/icons/svg/25/25440.svg">  
            </span>
            <span class="btn icon" data-action="justifyRight" title="Justify right">
              <img src="https://image.flaticon.com/icons/svg/25/25288.svg">  
            </span>
            <span class="btn icon" data-action="formatBlock" title="Justify block">
              <img 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 src="https://image.flaticon.com/icons/svg/25/25242.svg">  
        </span>
        <span class="btn icon" data-action="insertUnorderedList" title="Insert unordered list">
          <img src="https://image.flaticon.com/icons/svg/25/25648.svg">  
        </span>
        <span class="btn icon" data-action="outdent" title="Outdent">
          <img src="https://image.flaticon.com/icons/svg/25/25410.svg">  
        </span>
        <span class="btn icon" data-action="indent" title="Indent">
          <img 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 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 src="https://image.flaticon.com/icons/svg/25/25249.svg">
        </span>
        <span class="btn icon" data-action="removeFormat" title="Remove format">
          <img src="https://image.flaticon.com/icons/svg/25/25454.svg">  
        </span>
      </div>
      
      <div class="box">
        <span class="btn icon" data-action="code" title="Show HTML-Code">
          <img 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. CSS

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;
  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. JavaScript

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 wir eine UI deaktivieren, die bei manchen Kommandos benötigen wird.

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

Damit ist unser Editor nun schon funktionsfähig. Was allerdings noch fehlt, ist die Umschaltung zwischen der HTML Ansicht und der visuellen Ansicht. Dazu müssen wir abfragen, ob die action gleich code lautet.

if(action === 'code') {
}

Wenn dieser Fall eintritt, holen wir uns den Inhalt aus der HTML Ansicht und der visuellen Ansicht, blenden jeweils die andere ein und tauschen die Inhalte.

// custom action
if (action === 'code') {
  const contentArea = editor.getElementsByClassName('content-area')[0];
  const visuellView = contentArea.getElementsByClassName('visuell-view')[0];
  const htmlView = contentArea.getElementsByClassName('html-view')[0];

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

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

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

  return false;
}

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;
    
    // custom action
    if(action === 'code') {
      const contentArea = editor.getElementsByClassName('content-area')[0];
      const visuellView = contentArea.getElementsByClassName('visuell-view')[0];
      const htmlView = contentArea.getElementsByClassName('html-view')[0];
      
      if(this.classList.contains('active')) { // show visuell view
        visuellView.innerHTML = htmlView.value;
        htmlView.style.display = 'none';
        visuellView.style.display = 'block';

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

        this.classList.add('active'); 
      }
      
      return false;
    }
    
    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. 🙂

NEW 🚀
NEW 🚀
 

Schreib einen Kommentar

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