人人都會寫的富文本編輯器

vm-editor

這個原本是給 vm-manager 寫的一個富文本編輯器,後來以爲獨立出來維護比較方便,就單獨分離出來放到NPM。之因此說人人都會寫, 是由於這個組件實現起來確實比較簡單,不須要很厲害的Js水平, 基本是對document.execCommand()指令的綁定。在此將過程分享給你們html

預覽地址 https://luosijie.github.io/vm-editor/vue

源碼地址 https://github.com/luosijie/vm-editorgit

安裝

npm install --save vm-editor

使用

//upload綁定事件將編譯的html字符傳給父組件
<VmEditor @upload="showHtml"></VmEditor>

<script>
import VmEditor from 'vm-editor'
export default {
  ...
  methods: {
    showHtml: function(data){
      console.log(data)
    }
  }
}
</script>

準備

由於是Vue組件, 因此寫這樣的一個組件,須要掌握的知識點有:github

  1. Vue單文件組件開發
  2. execCommand指令

組件結構

組件結構圖

組件由 菜單部分內容區域 2大部分組成, 其中菜單區域又由各類 指令按鈕 組成,部分指令按鈕還有下拉選項npm

指令按鈕

指令按鈕是 execConmand 的裝載器,須要實現如下功能瀏覽器

  1. 劃過背景變灰
  2. 顯示按鈕圖標
  3. 部分按鈕須要實現點擊展開下拉菜單
<template>
  <button class="vm-editor-button" :class="{ active: slot }">
    // 顯示按鈕圖標
    <img :src="require('../assets/iconimg/' + icon + '.png')" height="16" width="16" alt="" @click="showSlot">
    <!-- <i :class="icon" @click="showSlot"></i> -->
    // 部分按鈕須要實現點擊展開下拉菜單
    <slot v-if="slot"></slot>
  </button>
</template>
<style>
  ...
  // 劃過顯示背景
  button.vm-editor-button:hover{        
    background-color: #eee;
  }
  ...
</style>
export default {
  name: 'VmEditorButton',
  props: {
    icon: {
      type: String,
      default: 'heading'
    }
  },
  data: function () {
    return {
      slot: false
    }
  },
  methods: {
    showSlot () {
      this.slot === false ? this.slot = true : this.slot = false
    }
  }
}
</script>

菜單區域

菜單區域
菜單區域放置按鈕,主要實現的功能是app

  1. 將execCommand指令綁定到按鈕中
  2. 實現點擊上傳按鈕編譯html
<template>
  <div class="vm-editor-menu">
    // 引入按鈕組建, 經過 **click事件** 綁定封裝的 **execCommand方法** 實現樣式的變化 
    <VmEditorButton icon="paragraph" @click.native="execCommand('formatBlock', '<p>')">
    </VmEditorButton>
    <VmEditorButton icon="heading">
      <VmEditorDropdown>
        // 這是部分按鈕須要下拉菜單功能
        <ul class="vm-editor-ul">
          <li @click="execCommand('formatBlock', '<h1>')">
            <h1>H1</h1>
          </li>
          <li @click="execCommand('formatBlock', '<h2>')">
            <h2>H2</h2>
          </li>
          <li @click="execCommand('formatBlock', '<h3>')">
            <h3>H3</h3>
          </li>
          <li @click="execCommand('formatBlock', '<h4>')">
            <h4>H4</h4>
          </li>
          <li @click="execCommand('formatBlock', '<h5>')">
            <h5>H5</h5>
          </li>
        </ul>
      </VmEditorDropdown>
    </VmEditorButton>
    // 省略其餘按鈕代碼
    ...
    <slot></slot>
  </div>
</template>
<style>
  ...
</style>
<script>
...
export default {
  name: 'VmEditorMenu',
  components: {
    VmEditorButton,
    VmEditorDropdown,
    VmEditorAddlink,
    VmEditorAddimage,
    VmEditorFontcolor
  },
  methods: {
    // 封裝 document.execCommand 指令
    execCommand: function (commandName, valueArgument) {
      // let body = document.querySelector('.body');
      if (!valueArgument) {
        valueArgument = null
      }
      document.execCommand('styleWithCSS', null, true)
      document.execCommand(commandName, false, valueArgument)
    },
    // 插入圖片功能
    setImage: function (evt) {
      let reader = new FileReader()
      let file = evt.target.files[0]
      reader.readAsDataURL(file)
      reader.onload = function (evt) {
        let base64Image = evt.target.result
        document.execCommand('insertImage', false, base64Image)
      }
    }
  }
}
</script>

主組件

主組件
主組件就是將 菜單組件內容區域 整合在一塊兒編輯器

另外還要實現導出html的功能ide

<div class="vm-editor">
  <VmEditorMenu>
    <div class="global-control">
      // 處處html的按鈕,放在這裏由於,須要獲取 內容區域 的html數據
      <VmEditorButton icon="upload" @click.native="uploadHtml"></VmEditorButton>
    </div>
  </VmEditorMenu>
  // 內容區域 只要設置 **contenteditable="true"** 就能夠了,其餘的交給指令去作
  <div class="content" contenteditable="true" v-html="html">
  </div>
</div>
<style>
 ...
</style>
<script>
  name: 'VmEditor',
  components: {
    VmEditorMenu,
    VmEditorButton
  },
  data: function () {
    return {
      html: 'Please Enter ...'
    }
  },
  methods: {
     導出html數據
    // 目前 內容區域 的樣式都是 **CSS樣式**, 導出時須要轉化爲 **內聯樣式**
    uploadHtml: function () {
      // 獲取各個模塊的 CSS樣式
      let style = {
        ul: `
              margin: 10px 20px;
              list-style-type: square;
              padding: 0;
            `,
        ol: `
              margin: 10px 20px;
              list-style-type: decimal;
              padding: 0;
            `,
        li: `
              display: list-item;
              padding: 0;
            `,
        hr: `
              margin: 15px 0;
              border-top: 1px solid #eeeff1;
            `,
        pre: `
              display: block;
              margin: 10px 0;
              padding: 8px;
              border-radius: 4px;
              background-color: #f2f2f2;
              color: #656565;
              font-size: 14px;
             `,
        blockquote: `
                      display: block;
                      border-left: 4px solid #ddd;
                      margin: 15px 0;
                      padding: 0 15px;
                    `,
        img: `
               margin: 20px 0;
             `,
        a: `
            color: #41b883;
           `
      }
      let html = document.getElementsByClassName('content')[0]
      let htmlContainerParent = document.createElement('div')
      let htmlContainer = document.createElement('div')
      let tagNames = Object.keys(style)
      // 遍歷html節點並插入對應的內聯樣式 
      for (let i = 0; i < tagNames.length; i++) {
        let _tagNames = html.getElementsByTagName(tagNames[i])
        if (_tagNames.length > 0) {
          for (let j = 0; j < _tagNames.length; j++) {
            _tagNames[j].style = style[tagNames[i]]
          }
        }
      }
      htmlContainer.style = `
                              text-align: left;
                              padding: 15px;
                              font-size: 16px; 
                            `
      htmlContainer.innerHTML = html.innerHTML
      htmlContainerParent.appendChild(htmlContainer)
      
      // 註冊自定義事件 **upload**
      this.$emit('upload', htmlContainerParent.innerHTML)
    }
  }
}
</script>

其餘組件

其餘的組建主要是按鈕下拉菜單, 由於每一個都不同,因此要獨立出來學習

  1. vm-editor-addimage: 插入圖片
  2. vm-editor-addlink: 插入連接
  3. vm-editor-fontcolor: 修改顏色

不足之處

由於這個富文本編輯器的開發時間比較短,沒有認真研究相似優秀插件的源碼 也沒有 深刻調研過富文本編輯器的需求。
只是參考了一些同類編輯器的實現效果和UI風格,好比simditor,而後簡單實現了一下功能。

因此確定還有不少須要改善的地方,比較明顯的有:

  1. 瀏覽器的兼容
  2. 表格功能的實現

無論怎樣,僅供你們學習使用

先這樣了, 歡迎star

相關文章
相關標籤/搜索