Program your own WYSIWYG editor – in 10 minutes!

Program your own WYSIWYG editor – in 10 minutes!

Are you annoyed by missing or unnecessary functions in WYSIWYG editors? No problem! Here I show you how you can program your own WYSIWYG editor.

WYSIWYG stands for “What you see is what you get”. This refers to text editors that directly display a text with all formatting and we can change the formatting at will. These are then displayed to us in real time.

Many of the available editors, like TinyMCE, work really well and are great for most projects. However, you might find one or the other editor a bit overloaded, or you might just be tempted to program your own WYSIWYG editor.

The following demo is created with pure HTML, CSS and pure JavaScript. In the next steps I will go into the implementation of this WYSIWYG editor in detail and at the end you will be able to program your own editor!

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

1. HTML

Our main task in HTML is to create the Editor Toolbar. For this we have an outer container .editor. This includes a container for the toolbar .toolbar and a container for the different views (Visual & HTML view) .content-area.

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

1.1 The Toolbar

I have arranged the toolbar in two lines (.line), but it can be any more. In addition there are several boxes .box in each line for a rough outline of the formatting possibilities.

In such a box there is always a span element with a data action. This data action contains the command that is to be executed later on the marked text. This is how the two toolbar lines look like in HTML:

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

The Data Action is the command that is executed later on the selected text. There is a list of MDN web docs here. So you can easily add more commands to the editor here.

1.2 Views (Content)

In the content area we have two areas: An HTML view and a visual view. For this we create a container .visuell-view, which additionally gets the property contenteditable. This property allows us to edit content directly inline without input. Try this out if you don’t know this function.

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

We also add a textarea .html-view for the HTML view, because we want to switch between HTML and visual view later in the editor.

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

And this is how the entire HTML code looks at a glance:

<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 my editor I use icons from Flaticon. Therefore I have to insert a corresponding hint on my page. If you use your own icons, of course this is not the case for you.

<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

I’ve converted my SCSS code to normal CSS so that everyone can understand it.

But I won’t explain anything further, because CSS basics should be clear, if you want to program such an editor. Of course you can also use your own styles here.

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

In JavaScript we now have to assign functions to our toolbar. First we declare and initialize important elements of our editor:

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

In order not to program each function individually, we have already created a data action with the command in the HTML. Now we simply register the click on these buttons in a loop:

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

JavaScript provides us with a great function document.execCommand(). This allows us to apply our action to the marked text. The documentation for this function can be found here.

We read the action from the data action (in HTML) and execute the command. The second parameter must be set to false to disable a UI that will be required for some commands.

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

Now our editor is already functional. What is still missing, however, is the switch between the HTML view and the visual view. For this we have to ask if the action is code or not.

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

If this happens, we get the content from the HTML view and the visual view, fade in the other and swap the content.

// 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;
}

And here once again the complete 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. Conclusion

As you can see now, you can program your own WYSIWYG editor relatively quickly and style and program according to your ideas. If you liked this post, I would be happy if you support my blog with your repeated visits. 🙂

NEW 🚀
NEW 🚀
 

Leave a comment

Your email address will not be published. Required fields are marked *