A Lightweight and Customizable WYSIWYG Editor In Pure JavaScript | SunEditor

WYSIWYG HTML editor based on pure JavaScript, suneditor is a lightweight, versatile, customizable, pure JavaScript WYSIWYG textual content(text) editor in your web functions.

Key Features: 

  • Paste from Microsoft Word and Excel.
  • Define, merge, and split a custom table.
  • Include media, upload photos.
  • CodeMirror, KaTeX can be used.

How to make use of it:

Load the needed suneditor.css and suneditor.js within the HTML doc.

<link href="suneditor/css/suneditor.css" rel="stylesheet">
<script src="suneditor/js/suneditor.js"></script>

Create a traditional textarea component for the WYSIWYG editor.

<textarea id="editor">Hello World!</textarea>

Create a new editor from the textarea aspect. Done.


All default options to customize the WYSIWYG editor.

SUNEDITOR.create('editor', {

  // plugins to load
  plugins: [
    math, // You must add the 'katex' library at options to use the 'math' plugin.

  // set the initial value
  value: '',

  // Add tags to the default tags whitelist of editor.
  // _defaultTagsWhitelist : 'br|p|div|pre|blockquote|h[1-6]|ol|ul|li|hr|figure|figcaption|img|iframe|audio|video|table|thead|tbody|tr|th|td|a|b|strong|var|i|em|u|ins|s|span|strike|del|sub|sup'
  addTagsWhitelist: '',

  // Whitelist of tags when pasting. 
  // _editorTagsWhitelist  : _defaultTagsWhitelist + addTagsWhitelist
  // ex) 'p|h[1-6]'
  pasteTagsWhitelist: _editorTagsWhitelist

  // Add attributes whitelist of tags that should be kept undeleted from the editor.
  // -- Fixed whitelist --
  // Native attributes: 'contenteditable|colspan|rowspan|target|href|src|class|type'
  // Editor attributes: 'data-format|data-size|data-file-size|data-file-name|data-origin|data-align|data-image-link|data-rotate|data-proportion|data-percentage|origin-size'
  // ex) {
  //  'all': 'style', // Apply to all tags
  //  'input': 'checked' // Apply to input tag
  // }              
  attributesWhitelist: '',

  // language object
  lang: lang['en'],

  // change default formatBlock array.
  formats: ['p', 'div', 'blockquote', 'pre', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'],

  // show the number of characters in the editor.   
  charCounter: false,

  // null || 'char' || 'byte' || 'byte-html'
  charCounterType: 'char',

  // text to be displayed in the "charCounter" area of the bottom bar
  charCounterLabel: null,

  // the maximum number of characters allowed to be inserted into the editor.
  maxCharCount: null,

  // the min-width size of the editor.
  minWidth: null,

  // the max-width size of the editor.
  maxWidth: null,

  // the size of the total uploadable images (in bytes).
  imageUploadSizeLimit: null,

  // if true, multiple images can be selected.
  imageMultipleFile: false,

  // allowed extensions like '.jpg, .png ..'
  imageAccept: "*",

  // The url of the image gallery, if you use the image gallery.
  // When "imageUrlInput" is true, an image gallery button is created in the image modal.
  // You can also use it by adding 'imageGallery' to the button list.
  imageGalleryUrl: null,

  // 'classic', 'inline', 'balloon', 'balloon-always'
  mode: 'classic',

  // toolbar width
  toolbarWidth: 'max-content',

  // 'cell', 'top'
  tableCellControllerPosition: 'cell',

  // if true, disables the interaction of the editor and tab key.
  tabDisable: false,

  // You can disable shortcuts.
  // e.g. ['bold', 'strike', 'underline', 'italic', 'undo', 'indent']
  shortcutsDisable: [],
  // If false, hide the shortcuts hint.
  shortcutsHint: true,

  // A custom HTML selector placing the toolbar inside.
  toolbarContainer: null,

  // Sets to -1 or false or null to turn off
  // Sticky Toolbar
  // Default: 0px (offset)
  stickyToolbar: 0,

  // the position property of suneditor.   
  position: null,

  // places content in the iframe
  iframe : false,

  // allows the usage of HTML, HEAD, BODY tags and DOCTYPE declaration.
  fullPage: false,

  // CSS file to apply inside the iframe
  iframeCSSFileName: 'suneditor',

  // CodeMirror option object
  codeMirror: null,

  // Katex library option object
  codeMirror: null,

  // Shows the bottom resizing bar.
  resizingBar: true,

  // Font Family array
  font: ['Arial', 'Comic Sans MS', 'Courier New', 'Impact', 'Georgia','tahoma', 'Trebuchet MS', 'Verdana'],

  // Font Size array
  fontSize: [8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72],

  // Font size unit
  fontSizeUnit: 'px',

  // Enables video resizing
  videoResizing: true,

  // width/height of the video
  videoWidth: 560,
  videoHeight: '56.25%',

  // If true, video size can only be scaled by percentage.
  videoSizeOnlyPercentage: false,

  // Choose whether to video rotation buttons display.
  videoRotation: false,

  // The default aspect ratio of the video.
  videoRatio: 0.5625,

  // Video ratio selection options.
  videoRatioList:  [ {name: 'Classic Film 3:2', value: 0.6666}, {name: 'HD', value: 0.5625} ],

  // Choose whether the video height input is visible.
  videoHeightShow: true,

  // Choose whether the video ratio options is visible.
  videoRatioShow: true,

  // the query string of a YouTube embedded URL. 
  youtubeQuery: '',

  // whether to create a file input tag in the video upload window.
  videoFileInput: false,

  // whether to create a video url input tag in the video upload window.
  // if the value of videoFileInput is false, it will be unconditionally.
  videoUrlInput: true,

  // Http Header when uploading videos.  
  videoUploadHeader: null,

  // the video upload to server mapping address.
  videoUploadUrl: null,

  // the size of the total uploadable videos (in bytes).
  videoUploadSizeLimit:  null,

  // if true, multiple videos can be selected. 
  videoMultipleFile:  false,

  // define "Attributes" of the video tag.
  videoTagAttrs: null,

  // define "Attributes" of the iframe tag
  videoIframeAttrs: null,

  // allowed extensions like '.mp4, .avi ..'
  videoAccept: "*",

  // default width of the audio frame.  
  audioWidth: '300px',

  // default height of the audio frame. 
  audioHeight: default,

  // whether to create a file input tag in the audio upload window
  audioFileInput: false,

  // whether to create a audio url input tag in the audio upload window.
  audioUrlInput: true,

  // Http Header when uploading audios. 
  audioUploadHeader: null,

  // upload url
  audioUploadUrl: null,

  // the size of the total uploadable audios (in bytes).
  // invokes the "onAudioUploadError" method.
  audioUploadSizeLimit: null,

  // if true, multiple audios can be selected.
  audioMultipleFile: false,

  // define "Attributes" of the audio tag.  
  audioTagAttrs: null,

  // allowed extensions like '.mp3, .wav ..'
  audioAccept: "*",

  // default protocol for the links. ('link', 'image', 'video', 'audio')
  linkProtocol: null,

  // the placeholder text.  
  placeholder: null,

  // custom icons
  // {
  //   bold: 'B',
  //   table: '',
  //   insert_row_above: ''
  // }
  icons: null,

  // Choose whether the image height input is visible.
  imageHeightShow: true,

  // enables image resizing
  imageResizing: true,

  // image width/height
  imageWidth: 'auto',
  imageHeight: 'auto',

  // If true, image size can only be scaled by percentage
  imageSizeOnlyPercentage: true,

  // Choose whether the image height input is visible.
  imageHeightShow: true,

  // Shows image rotation buttons
  imageRotation: false,

  // image file input
  imageFileInput: true,

  // image url input
  imageUrlInput: true,

  // image upload url
  imageUploadUrl: null,

  // Http Header when uploading images
  imageUploadHeader: null,

  // height/width of the editor
  height: '',
  width: '',

  // min height/width of the editor
  minHeight: null,
  minWidth: null,

  // color array of color picker
  // e.g. [['#ccc', '#dedede', 'OrangeRed', 'Orange', 'RoyalBlue', 'SaddleBrown'], ['SlateGray', 'BurlyWood', 'DeepPink', 'FireBrick', 'Gold', 'SeaGreen']]
  colorList: null,

  // line-height array
  lineHeights: [
    {text: '1', value: 1},
    {text: '1.15', value: 1.15},
    {text: '1.5', value: 1.5},
    {text: '2', value: 2}

  // Displays the current node structure to resizingBar
  showPathLabel: true,

  // Size of background area when activating dialog window
  popupDisplay: '',

  // CSS display property
  display: 'block',

  // show/hide toolbar icons
  buttonList: [
    ['undo', 'redo'],
    ['font', 'fontSize', 'formatBlock'],
    ['paragraphStyle', 'blockquote'],
    ['bold', 'underline', 'italic', 'strike', 'subscript', 'superscript'],
    ['fontColor', 'hiliteColor', 'textStyle'],
    '/', // Line break
    ['outdent', 'indent'],
    ['align', 'horizontalRule', 'list', 'lineHeight'],
    ['table', 'link', 'image', 'video', 'audio' /** ,'math' */], // You must add the 'katex' library at options to use the 'math' plugin.
    /** ['imageGallery'] */ // You must add the "imageGalleryUrl".
    ['fullScreen', 'showBlocks', 'codeView'],
    ['preview', 'print'],
    ['save', 'template']

  // execute a function when the save button is clicked.
  callBackSave: function(){}

API strategies:

var suneditor = SUNEDITOR.create('Editor');

// Copies the contents of the suneditor into a [textarea]

// Updates options

// Gets the suneditor's context object. Contains settings, plugins, and cached element objects

// Gets the contents of the suneditor

// Changes the contents of the suneditor
editor.setContents('set contents');

// Adds content to the suneditor
editor.appendContents('append contents');

// Inserts an HTML element or HTML string or plain string at the current cursor position
suneditor.insertHTML('<img src="https://suneditor.com/sample/img/sunset.jpg">');

// Change the contents of the suneditor
suneditor.setContents('set contents');

// Add content to the suneditor
suneditor.appendContents('append contents');

// Upload images using image plugin

// Disable the suneditor

// Enabled the suneditor

// Hide the suneditor

// Show the suneditor
// Destroy the suneditor

// Open a notice area
suneditor.noticeOpen('test notice');

// Close a notice area

// Gets a list of images uploaded to the editor
 * {
 *  src: imgage src
 *  index: data index
 *  name: file name
 *  size: file size
 *  select: select function
 *  delete: delete function
 * }

Event functions.

editor.onload = function (core, reload) {
    console.log('onload-core', core)
    console.log('onload-reload', reload)    

suneditor.onScroll = function (e) { console.log('onScroll', e) }

suneditor.onClick = function (e) { console.log('onClick', e) }

editor.onInput = function (e, core) { console.log('onInput', e) }

suneditor.onKeyDown = function (e) { console.log('onKeyDown', e) }

suneditor.onKeyUp = function (e) { console.log('onKeyUp', e) }

suneditor.onDrop = function (e) { console.log('onDrop', e) }

suneditor.onChange = function (contents) { console.log('onChange', contents) }

editor.onFocus = function (e, core) { console.log('onFocus', e) }

editor.onBlur = function (e, core) { console.log('onBlur', e) }

editor.onPaste = function (e, cleanData, maxCharCount) { console.log('onPaste', e, cleanData, maxCharCount) }

editor.onCopy = function (e, clipboardData, core) { console.log('onCopy', e) }

editor.onCut = function (e, clipboardData, core) { console.log('onCut', e) }

// Called before the image is uploaded
// If false is returned, no image upload is performed.
 * files: Files array
 * info: Input information
 * core: Core object
 * return {Boolean}
editor.onImageUploadBefore: function (files, info, core) {
    console.log('files', files);
    console.log('info', info);
    return Boolean

// It replaces the default callback function of the image upload
 * response: Response object
 * info (Input information): {
 * - linkValue: Link url value
 * - linkNewWindow: Open in new window Check Value
 * - inputWidth: Value of width input
 * - inputHeight: Value of height input
 * - align: Align Check Value
 * - isUpdate: Update image if true, create image if false
 * - currentImage: If isUpdate is true, the currently selected image.
 * }
 * core: Core object
editor.imageUploadHandler = function (response, info, core) {
    // Example of upload method
    const res = JSON.parse(response.responseText);
    // Error
    if (res.errorMessage) {
        if (typeof editor.onImageUploadError === 'function') {
            if (core.onImageUploadError(res.errorMessage, res.result)) {
                core.notice.open.call(core, res.errorMessage);
        } else {
            core.notice.open.call(core, res.errorMessage);
         * You can do the same thing using the core private function.
         * The core._imageUploadError function returns false when "editor.onImageUploadError" function is not defined.
        // if (core._imageUploadError(res.errorMessage, res.result)) {
        //     core.notice.open.call(core, res.errorMessage);
        // }
    // Success
    else {
        const fileList = res.result;
        const imagePlugin = core.plugins.image;

        for (let i = 0, len = fileList.length, file; i < len; i++) {
            // The file object must have name and size attributes.
            file = {name: fileList[i].name, size: fileList[i].size};
            // For existing image updates, the "info" attributes are predefined in the element.
            // The "imagePlugin.update_src" function is only changes the "src" attribute of an image.
            if (info.isUpdate) imagePlugin.update_src.call(core, fileList[i].url, info.currentImage, file);
            // The image is created and a format element(p, div..) is added below it.
            else imagePlugin.create_image.call(core, fileList[i].url, info.linkValue, info.linkNewWindow, info.inputWidth, info.inputHeight, info.align, file);

// An event when toggling between code view and wysiwyg view.
 * isCodeView: Whether the current code view mode
 * core: Core object
editor.toggleCodeView = function (isCodeView, core) {
  console.log('isCodeView', isCodeView);

// An event when toggling full screen.
 * isFullScreen: Whether the current full screen mode
 * core: Core object
editor.toggleFullScreen = function (isFullScreen, core) {
  console.log('isFullScreen', isFullScreen);

// Called when the video(iframe) is is uploaded, updated, deleted
* targetElement: Current iframe element
* index: Uploaded index
* state: Upload status ('create', 'update', 'delete')
* videoInfo: {
* - index: data index
* - select: select function
* - delete: delete function
* - element: iframe element
* - src: src attribute of iframe tag
* }
* remainingFilesCount: Count of remaining files to upload (0 when added as a url)
* core: Core object
editor.onVideoUpload = function (targetElement, index, state, videoInfo, remainingFilesCount, core) {
  console.log(`targetElement:${targetElement}, index:${index}, state('create', 'update', 'delete'):${state}`)
  console.log(`videoInfo:${videoInfo}, remainingFilesCount:${remainingFilesCount}`)

// Called before the editor's default event action
editor.showInline = function (toolbar, context) {
  console.log('toolbar', toolbar);
  console.log('context', context);

// Called when the image is uploaded or the uploaded image is deleted.
suneditor.onImageUpload = function (targetImgElement, index, isDelete, imageInfo) {
  console.log('targetImgElement :' + targetImgElement + ', index : ' + index + ', isDelete : ' + isDelete)

// Called when the image is upload failed.
// If you return false, the default notices are not called.
suneditor.onImageUploadError = function (errorMessage, result) {

Minimal WYSIWYG Editor In Pure JavaScript, simple javascript HTML editor, SunEditor Github

See Demo And Download

Official Website(JiHong88): Click Here

This superior jQuery/javascript plugin is developed by JiHong88. For extra Advanced Usage, please go to the official website.