자신만의 WYSIWYG 편집기 구축(HTML, CSS 및 순수 JavaScript)

WYSIWYG 편집기에서 누락되거나 불필요한 기능으로 인해 짜증이 납니까? 괜찮아요! 여기에서는 HTML, CSS 및 JavaScript를 사용하여 완전한 기능을 갖춘 자신만의 WYSIWYG 편집기를 만드는 방법을 보여줍니다.

WYSIWYG는 "What You See Is What You Get"의 약자입니다. 이것은 모든 서식이 있는 텍스트를 직접 표시하는 텍스트 편집기를 말하며 원하는 대로 서식을 변경할 수 있습니다. 서식 있는 텍스트 편집기라고도 합니다.

목차


  • 1. Design the HTML framework
  • 1.1 The Toolbar
  • 1.2 Visual and HTML view
  • 1.3 Insert modal (pop-up) for links
  • 1.4 Full HTML code

  • 2. Style WYSIWYG editor
  • 3. JavaScript의 프로그래밍 기능
  • 3.1 Declare variables
  • 3.2 Assign functions to toolbar buttons
  • 3.3 Program link modal (pop-up) functionality
  • 3.4 Enable toolbar buttons when formatting is selected
  • 3.5 Remove formatting when pasting text (paste event)
  • 3.6 Insert p tag as line break
  • 3.7 Full JavaScript Code

  • 4. Conclusion

  • TinyMCE 과 같이 사용 가능한 많은 편집기가 정말 잘 작동하며 대부분의 프로젝트에 적합합니다. 그러나 하나 또는 다른 편집기가 약간 오버로드되거나 너무 복잡하거나 자신만의 WYSIWYG 편집기를 프로그래밍하고 싶을 수 있습니다.

    다음 데모는 순수 HTML, CSS 및 순수 JavaScript로 생성됩니다. 다음 단계에서는 이 WYSIWYG 편집기의 구현에 대해 자세히 설명하고 마지막에는 자신의 편집기를 프로그래밍할 수 있습니다.

    다음은 함께 코딩할 편집기의 실행 중인 데모 버전입니다.



    Also on my blog webdeasy.de I use this rich text editor for the comments! 🙂



    1. HTML 프레임워크 디자인

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

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

    1.1 도구 모음

    I have arranged the toolbar in two lines ( .line ), but there can be as many as you like. There are also several .box boxes in each line for a rough outline of the formatting options.

    In such a box there is always a span element with a data action ( data-action ). This data action contains the command that is to be executed later on the selected text. In addition, some elements have a data tag name ( data-tag-name ). This is important later so that we can set the button active if the current text selection has a certain formatting.

    This is what the two toolbar lines look like in HTML:

    <div class="line">
      <div class="box">
        <span class="editor-btn icon smaller" data-action="bold" data-tag-name="b" title="Bold">
          <img src="https://img.icons8.com/fluency-systems-filled/48/000000/bold.png"/>
        </span>
        <span class="editor-btn icon smaller" data-action="italic" data-tag-name="i" title="Italic">
          <img src="https://img.icons8.com/fluency-systems-filled/48/000000/italic.png"/>
        </span>
        <span class="editor-btn icon smaller" data-action="underline" data-tag-name="u" title="Underline">
          <img src="https://img.icons8.com/fluency-systems-filled/48/000000/underline.png"/>
        </span>
        <span class="editor-btn icon smaller" data-action="strikeThrough" data-tag-name="strike" title="Strike through">
          <img src="https://img.icons8.com/fluency-systems-filled/30/000000/strikethrough.png"/>
        </span>
      </div>
      <div class="box">
        <span class="editor-btn icon has-submenu">
          <img src="https://img.icons8.com/fluency-systems-filled/48/000000/align-left.png"/>
          <div class="submenu">
            <span class="editor-btn icon" data-action="justifyLeft" data-style="textAlign:left" title="Justify left">
              <img src="https://img.icons8.com/fluency-systems-filled/48/000000/align-left.png"/>
            </span>
            <span class="editor-btn icon" data-action="justifyCenter" data-style="textAlign:center" title="Justify center">
              <img src="https://img.icons8.com/fluency-systems-filled/48/000000/align-center.png"/>
            </span>
            <span class="editor-btn icon" data-action="justifyRight" data-style="textAlign:right" title="Justify right">
              <img src="https://img.icons8.com/fluency-systems-filled/48/000000/align-right.png"/>
            </span>
            <span class="editor-btn icon" data-action="formatBlock" data-style="textAlign:justify" title="Justify block">
              <img src="https://img.icons8.com/fluency-systems-filled/48/000000/align-justify.png"/>
            </span>
          </div>
        </span>
        <span class="editor-btn icon" data-action="insertOrderedList" data-tag-name="ol" title="Insert ordered list">
          <img src="https://img.icons8.com/fluency-systems-filled/48/000000/numbered-list.png"/>
        </span>
        <span class="editor-btn icon" data-action="insertUnorderedList" data-tag-name="ul" title="Insert unordered list">
          <img src="https://img.icons8.com/fluency-systems-filled/48/000000/bulleted-list.png"/>
        </span>
        <span class="editor-btn icon" data-action="outdent" title="Outdent" data-required-tag="li">
          <img src="https://img.icons8.com/fluency-systems-filled/48/000000/outdent.png"/>
        </span>
        <span class="editor-btn icon" data-action="indent" title="Indent">
          <img src="https://img.icons8.com/fluency-systems-filled/48/000000/indent.png"/>
        </span>
      </div>
      <div class="box">
        <span class="editor-btn icon" data-action="insertHorizontalRule" title="Insert horizontal rule">
          <img src="https://img.icons8.com/fluency-systems-filled/48/000000/horizontal-line.png"/>
        </span>
      </div>
    </div>
    <div class="line">
      <div class="box">
        <span class="editor-btn icon smaller" data-action="undo" title="Undo">
          <img src="https://img.icons8.com/fluency-systems-filled/48/000000/undo--v1.png"/>
        </span>
        <span class="editor-btn icon" data-action="removeFormat" title="Remove format">
          <img src="https://img.icons8.com/fluency-systems-filled/48/000000/remove-format.png"/>
        </span>
      </div>
      <div class="box">
        <span class="editor-btn icon smaller" data-action="createLink" title="Insert Link">
          <img src="https://img.icons8.com/fluency-systems-filled/48/000000/add-link.png"/>
        </span>
        <span class="editor-btn icon smaller" data-action="unlink" data-tag-name="a" title="Unlink">
          <img src="https://img.icons8.com/fluency-systems-filled/48/000000/delete-link.png"/>
        </span>
      </div>
      <div class="box">
        <span class="editor-btn icon" data-action="toggle-view" title="Show HTML-Code">
          <img src="https://img.icons8.com/fluency-systems-filled/48/000000/source-code.png"/>
        </span>
      </div>
    </div>
    
    In my editor I use icons from Icons8 . 따라서 내 페이지에 해당 메모를 삽입해야 합니다. 자신의 아이콘을 사용하는 경우에는 필요하지 않습니다.

    데이터 작업은 선택한 텍스트에서 나중에 실행될 명령입니다. 이를 위해 list of MDN web docs이 있습니다. 따라서 여기에서 더 많은 명령으로 편집기를 쉽게 확장할 수 있습니다.

    1.2 시각적 및 HTML 보기

    In the content area we have two sections: An HTML view and a visual view. For this we create a container .visual-view , which also gets the property contenteditable . This property allows us to edit content directly inline without input. Feel free to try this out if you don’t know this feature.

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

    1.3 링크용 모달(팝업) 삽입

    This modal is opened when we want to insert a link. There you have the possibility to enter the link and choose if you want to open the link in a new window.

    <div class="modal">
      <div class="modal-bg"></div>
      <div class="modal-wrapper">
        <div class="close"></div>
        <div class="modal-content" id="modalCreateLink">
          <h3>Insert Link</h3>
          <input type="text" id="linkValue" placeholder="Link (example: https://webdeasy.de/)">
          <div class="row">
            <input type="checkbox" id="new-tab">
            <label for="new-tab">Open in new Tab?</label>
          </div>
          <button class="done">Done</button>
        </div>
      </div>
    </div>
    

    1.4 전체 HTML 코드

    ➡️ Check out the full code of the HTML file here

    2. 스타일 WYSIWYG 편집기

    I have converted my SCSS code into normal CSS here so that everyone can understand it.

    But I don’t explain anything else about this, because CSS basics should be clear, if you want to program such an editor. Of course you can also use your own styles here.
    ➡️
    Check out the full code of the CSS file here

    3. JavaScript의 프로그래밍 기능


    3.1 변수 선언

    In JavaScript we now have to implement some functions. To do this, we first declare and initialize important elements of our editor:

    const editor = document.getElementsByClassName('wp-webdeasy-comment-editor')[0];
    const toolbar = editor.getElementsByClassName('toolbar')[0];
    const buttons = toolbar.querySelectorAll('.editor-btn:not(.has-submenu)');
    const contentArea = editor.getElementsByClassName('content-area')[0];
    const visuellView = contentArea.getElementsByClassName('visuell-view')[0];
    const htmlView = contentArea.getElementsByClassName('html-view')[0];
    const modal = document.getElementsByClassName('modal')[0];
    

    3.2 도구 모음 버튼에 기능 할당

    To avoid programming each function individually we have already created a data action (data-action) in the HTML with the command. 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) {
      });
    }
    

    With the following line we read the action from the data action (in the HTML).

    let action = this.dataset.action;
    

    We include a switch-case statement because inserting a link and switching the HTML view and the visual view requires even more from us.

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

    For “normal” functions we use the execDefaultAction(action) function. There only the execCommand() function of JavaScript is executed with the data action of the respective button.

    function execDefaultAction(action) {
      document.execCommand(action, false);
    }
    
    JavaScript provides us with a great function document.execCommand() . This allows us to apply our action to the selected text. You can find the documentation for this function here .

    Note: The execCommand() function is deprecated, but is currently still supported by all browsers. As soon as there should be problems here, I will update the original post on my blog!


    execCommand() 의 두 번째 매개변수는 false 로 설정해야 합니다. 이를 통해 예를 들어 이전 Internet Explorer 버전에 표시되는 작은 UI를 비활성화합니다. 그러나 우리는 이것이 필요하지 않으며 Firefox 또는 Google Chrome은 어쨌든 이러한 기능을 지원하지 않습니다.

    HTML 보기와 시각적 보기 사이를 전환하려면 다른 보기를 페이드 인하고 내용을 바꿉니다.

    function execCodeAction(button, editor) {
      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'); 
      }
    }
    


    3.3 프로그램 링크 모달(팝업) 기능

    Next we want to be able to insert a link. For this purpose, I have already provided a modal in the HTML, i.e. a kind of pop-up.

    In the following function this is shown and the current text selection of the editor is saved via saveSelection() . This is necessary because we focus another element in our popup and thus our text selection in the editor disappears. After that, the close and submit buttons are created.

    function execLinkAction() {  
      modal.style.display = 'block';
      let selection = saveSelection();
      let submit = modal.querySelectorAll('button.done')[0];
      let close = modal.querySelectorAll('.close')[0];
    }
    
    function saveSelection() {
        if(window.getSelection) {
            sel = window.getSelection();
            if(sel.getRangeAt && sel.rangeCount) {
                let ranges = [];
                for(var i = 0, len = sel.rangeCount; i < len; ++i) {
                    ranges.push(sel.getRangeAt(i));
                }
                return ranges;
            }
        } else if (document.selection && document.selection.createRange) {
            return document.selection.createRange();
        }
        return null;
    }
    

    Now we need a click event to insert the link. There we additionally save whether the link should be opened in a new window, load the selection from the text editor again with restoreSelection() and then create a new a element for it in line 13 and set the link from the link input.

    In line 16 we then insert the created link around the text selection.

    The modal is then closed, the link input is cleaned and all events are deregistered.

    function execLinkAction() {  
      // ...  
      // done button active => add link
      submit.addEventListener('click', function() {
        let newTabCheckbox = modal.querySelectorAll('#new-tab')[0];
        let linkInput = modal.querySelectorAll('#linkValue')[0];
        let linkValue = linkInput.value;
        let newTab = newTabCheckbox.checked;    
    
        restoreSelection(selection);
    
        if(window.getSelection().toString()) {
          let a = document.createElement('a');
          a.href = linkValue;
          if(newTab) a.target = '_blank';
          window.getSelection().getRangeAt(0).surroundContents(a);
        }
        modal.style.display = 'none';
        linkInput.value = '';
    
        // deregister modal events
        submit.removeEventListener('click', arguments.callee);
        close.removeEventListener('click', arguments.callee);
      });  
      // ...
    }
    
    function restoreSelection(savedSel) {
        if(savedSel) {
            if(window.getSelection) {
                sel = window.getSelection();
                sel.removeAllRanges();
                for(var i = 0, len = savedSel.length; i < len; ++i) {
                    sel.addRange(savedSel[i]);
                }
            } else if(document.selection && savedSel.select) {
                savedSel.select();
            }
        }
    }
    

    We also give the close button a function that simply hides the modal, clears the link input, and deregisters the two events.

    function execLinkAction() {  
      // ...  
      close.addEventListener('click', function() {
        let linkInput = modal.querySelectorAll('#linkValue')[0];
    
        modal.style.display = 'none';
        linkInput.value = '';
    
        // deregister modal events
        submit.removeEventListener('click', arguments.callee);
        close.removeEventListener('click', arguments.callee);
      });
    }
    

    3.4 서식 선택 시 도구 모음 버튼 활성화

    If a text is selected in the WYSIWYG editor, we also want to highlight the corresponding formatting button. This way we always know what formatting a word or paragraph has.

    To do this, we insert the registration of the selectionchange 이벤트는 변수 선언 직후 맨 위에 있습니다.

    // add active tag event
    document.addEventListener('selectionchange', selectionChange);
    


    그런 다음 먼저 .active 클래스가 있는 모든 도구 모음 버튼에서 이 클래스를 제거하는 콜백 함수를 만듭니다. 그런 다음 선택 항목이 WYSIWYG 편집기(12행)에도 있는지 확인합니다. 그런 다음 parentTagActive() 함수를 호출하고 현재 텍스트 선택의 첫 번째 부모 HTML 태그를 전달합니다.

    function selectionChange(e) {
    
      for(let i = 0; i < buttons.length; i++) {
        let button = buttons[i];
    
        // don't remove active class on code toggle button
        if(button.dataset.action === 'toggle-view') continue;
    
        button.classList.remove('active');
      }
    
      if(!childOf(window.getSelection().anchorNode.parentNode, editor)) return false;
    
      parentTagActive(window.getSelection().anchorNode.parentNode);
    }
    

    parentTagActive() 함수를 재귀적으로 정의하여 활성 태그가 여러 개 있을 수 있습니다. 따라서 단어가 기울임꼴이면 굵게 표시되고 밑줄이 그어진 세 개의 도구 모음 버튼이 모두 활성화됩니다. 이러한 이유로 HTML의 개별 버튼에는 데이터 태그 이름( data-tag-name )이 지정되었습니다.

    텍스트 정렬은 같은 방식으로 처리되므로 텍스트가 왼쪽 정렬, 오른쪽 정렬, 양쪽 정렬 또는 가운데 정렬인지 확인할 수 있습니다.

    function parentTagActive(elem) {
      if(!elem ||!elem.classList || elem.classList.contains('visuell-view')) return false;
    
      let toolbarButton;
    
      // active by tag names
      let tagName = elem.tagName.toLowerCase();
      toolbarButton = document.querySelectorAll(`.toolbar .editor-btn[data-tag-name="${tagName}"]`)[0];
      if(toolbarButton) {
        toolbarButton.classList.add('active');
      }
    
      // active by text-align
      let textAlign = elem.style.textAlign;
      toolbarButton = document.querySelectorAll(`.toolbar .editor-btn[data-style="textAlign:${textAlign}"]`)[0];
      if(toolbarButton) {
        toolbarButton.classList.add('active');
      }
    
      return parentTagActive(elem.parentNode);
    }
    


    3.5 텍스트를 붙여넣을 때 서식 제거(붙여넣기 이벤트)

    When a user pastes something into the text editor, all formatting should be removed from this text, otherwise it can lead to unsightly formatting and complete design chaos. For this we register the paste event.

    // add paste event
    visuellView.addEventListener('paste', pasteEvent);
    

    The pasteEvent() function is then executed, which prevents normal pasting, gets the content from the user’s clipboard as plain text, and pastes it into our editor.

    function pasteEvent(e) {
      e.preventDefault();
    
      let text = (e.originalEvent || e).clipboardData.getData('text/plain');
      document.execCommand('insertHTML', false, text);
    }
    

    3.6 p 태그를 줄 바꿈으로 삽입

    Another improvement is to automatically insert a <p> tag as soon as the user presses Enter. For this we register the keypress event.

    // add paragraph tag on new line
    contentArea.addEventListener('keypress', addParagraphTag);
    
    The addParagraphTag() function is called. This checks whether the Enter key was pressed ( keycode 13). 그런 다음 현재 요소가 목록 요소( <p> -태그)가 아닌 경우 현재 블록은 자동으로 <li> -태그로 형식이 지정됩니다.

    function addParagraphTag(evt) {
      if (evt.keyCode == '13') {
    
        // don't add a p tag on list item
        if(window.getSelection().anchorNode.parentNode.tagName === 'LI') return;
        document.execCommand('formatBlock', false, 'p');
      }
    }
    


    3.7 전체 자바스크립트 코드

    ➡️ Check out the full code of the JavaScript file here

    4. 결론

    As you can see now, you can program your own WYSIWYG editor relatively easily and style and program it according to your ideas. If you liked this post, I would be happy if you support my blog by visiting it again. 🙂

    On this page I’m also using this WYSIWYG Editor for the WordPress Comments . 이것이 얼마나 쉽게 작동하는지 보려면 링크를 확인하십시오!

    좋은 웹페이지 즐겨찾기