如何用Vue实现简易的富文本编辑器,并支持Markdown语法
前端開發經常會用到富文本編輯器,比如CKEditor,動不動一個庫幾十M的代碼量,其中涉及許多你可能用不到的功能特性和相關設置,CKEditor最新版本的代碼倉庫就有接近2000個JS文件,300,000行代碼。
可是如果你只需要一個簡易版的編輯器,真的值得引入這么一個龐大的庫嗎?
今天我們從實現一個簡易版的編輯器帶大家了解一下其背后涉及到的原理。
開始
這個編輯器將要使用到markdown:一個簡潔語法并且自帶樣式的語言,而且遠比純HTML的輸入輸出要安全得多。
首先,我們需要一些依賴包。?@ts-stack/markdown?和?turndown,@ts-stack/markdown是用來將markdown語法轉化為HTML代碼顯示用的,而turndown是將HTML代碼轉化為markdown語言。
接下來,創建一個基礎的Vue組件,命名為WysiwygEditor.vue,在組件中添加一個div元素,并且將它的contenteditable屬性設置為true,然后添加一些Tailwind樣式去美化一下。
<!-- WysiwygEditor.vue --> <template><div><div@input="onInput"v-html="innerValue"contenteditable="true"class="wysiwyg-output outline-none border-2 p-4 rounded-lg border-gray-300 focus:border-green-300"/></div> </template><script> export default {name: 'WysiwygEditor',props: ['value'],data() {return {innerValue: this.value}},methods: {onInput(event) {this.$emit('input', event.target.innerHTML)}} } </script>然后使用該組件:
<!-- Some other component --> <template><!-- ... --><wysiwyg-editor v-model="someText" /><!-- ... --> </template> <!-- ... -->看起來像這樣
現在這個div元素的樣式看起來像textarea 標簽的效果了。
讓文本變為富文本
在編輯器的上面會有一些帶有bold,italic,underlined,headings,lists等文本的編輯按鈕。并且上面會有對應功能的圖標。可以通過安裝fontawesome icon來實現。然后對按鈕進行一些樣式設置。
.button {@apply border-2;@apply border-gray-300;@apply rounded-lg;@apply px-3 py-1;@apply mb-3 mr-3; } .button:hover {@apply border-green-300; }先將這些按鈕添加鼠標點擊后的監聽方法,后面我們會去實現每一個方法里的具體執行。
<!-- WysiwygEditor.vue --> <template><!-- ... --><div class="flex flex-wrap"><button @click="applyBold" class="button"><font-awesome-icon :icon="['fas', 'bold']" /></button><button @click="applyItalic" class="button"><font-awesome-icon :icon="['fas', 'italic']" /></button><button @click="applyHeading" class="button"><font-awesome-icon :icon="['fas', 'heading']" /></button><button @click="applyUl" class="button"><font-awesome-icon :icon="['fas', 'list-ul']" /></button><button @click="applyOl" class="button"><font-awesome-icon :icon="['fas', 'list-ol']" /></button><button @click="undo" class="button"><font-awesome-icon :icon="['fas', 'undo']" /></button><button @click="redo" class="button"><font-awesome-icon :icon="['fas', 'redo']" /></button></div><!-- ... --> </template> <!-- ... -->編輯器現在看起來是這樣了
現在看起來是不是越來越接近了。還缺少按鈕動作的執行方法。這里要用到document.execCommand,雖然MDN已經宣稱將廢棄該特性,但是大部分瀏覽器仍然支持。我們暫且還是使用它。
讓我們通過它來實現applyBold方法
methods: {// ...applyBold() {document.execCommand('bold')},// ... }非常簡潔明了,同樣,我們來實現其它方法
// ...applyItalic() {document.execCommand('italic')},applyHeading() {document.execCommand('formatBlock', false, '<h1>')},applyUl() {document.execCommand('insertUnorderedList')},applyOl() {document.execCommand('insertOrderedList')},undo() {document.execCommand('undo')},redo() {document.execCommand('redo')}// ...這里唯一需要說明的是applyHeading,因為我明確需要在此處指定所需的元素。使用這些命令后,可以預先對輸出的元素標簽進行一些樣式設置
.wysiwyg-output h1 {@apply text-2xl;@apply font-bold;@apply pb-4; } .wysiwyg-output p {@apply pb-4; } .wysiwyg-output p {@apply pb-4; } .wysiwyg-output ul {@apply ml-6;@apply list-disc; } .wysiwyg-output ol {@apply ml-6;@apply list-decimal; }有了一定樣式后,在輸入框中輸入一些內容
為了使得更美觀一點,把空行用空的段落標簽代替,以回車結束的內容歸為一個段落
// ...data() {return {innerValue: this.value || '<p><br></p>'}},mounted() {document.execCommand('defaultParagraphSeparator', false, 'p')},// ...添加markdown支持
如果我想直接在編輯器里寫markdown語法,暫時還不支持
# Hello, world!**Lorem ipsum dolor** _sit amet_* Some * Unordered * List1. Some 1. Ordered 1. List結果看起來是這樣
完全沒有任何樣式。別忘了,前面我們安裝了@ts-stack/markdown庫,現在可以使用了
import { Marked } from '@ts-stack/markdown'export default {name: 'WysiwygEditor',props: ['value'],data() {return {innerValue: Marked.parse(this.value) || '<p><br></p>'}},// ...我們把輸入的內容markdown語法轉化為HTML代碼之后,看起就正常了
同時還需要在組件傳出本文編輯器數據的時候,進行轉化,這里要用到前面安裝的turndown
import TurndownService from 'turndown'export default {// ...methods: {onInput(event) {const turndown = new TurndownService({emDelimiter: '_',linkStyle: 'inlined',headingStyle: 'atx'})this.$emit('input', turndown.turndown(event.target.innerHTML))}, // ...讓我們把編輯器中輸入的markdown語法文本在頁面中通過模板輸出后的效果
<!-- Some other component --> <template><!-- ... --><wysiwyg-editor v-model="someText" /><pre class="p-4 bg-gray-300 mt-12">{{ someText }}</pre><!-- ... --> </template>同步輸入輸出,內容是一致的,沒有任何問題
看起來一切正常,達到了我們想要的效果。下面是全部的代碼
<template><div><div class="flex flex-wrap"><button @click="applyBold" class="button"><font-awesome-icon :icon="['fas', 'bold']" /></button><button @click="applyItalic" class="button"><font-awesome-icon :icon="['fas', 'italic']" /></button><button @click="applyHeading" class="button"><font-awesome-icon :icon="['fas', 'heading']" /></button><button @click="applyUl" class="button"><font-awesome-icon :icon="['fas', 'list-ul']" /></button><button @click="applyOl" class="button"><font-awesome-icon :icon="['fas', 'list-ol']" /></button><button @click="undo" class="button"><font-awesome-icon :icon="['fas', 'undo']" /></button><button @click="redo" class="button"><font-awesome-icon :icon="['fas', 'redo']" /></button></div><div@input="onInput"v-html="innerValue"contenteditable="true"class="wysiwyg-output outline-none border-2 p-4 rounded-lg border-gray-300 focus:border-green-300"/></div> </template><script> import { Marked } from '@ts-stack/markdown' import TurndownService from 'turndown'export default {name: 'WysiwygEditor',props: ['value'],data() {return {innerValue: Marked.parse(this.value) || '<p><br></p>'}},mounted() {document.execCommand('defaultParagraphSeparator', false, 'p')},methods: {onInput(event) {const turndown = new TurndownService({emDelimiter: '_',linkStyle: 'inlined',headingStyle: 'atx'})this.$emit('input', turndown.turndown(event.target.innerHTML))},applyBold() {document.execCommand('bold')},applyItalic() {document.execCommand('italic')},applyHeading() {document.execCommand('formatBlock', false, '<h1>')},applyUl() {document.execCommand('insertUnorderedList')},applyOl() {document.execCommand('insertOrderedList')},undo() {document.execCommand('undo')},redo() {document.execCommand('redo')}} } </script>結論
只需要87行代碼便實現了一個簡易的富文本編輯器。雖然功能還是太簡單,但是最起碼我們知道了實現一個富文本編輯器后面的原理。后面需要增加功能就不是什么難事了。
分享硬核的編程知識,關注“太空編程”公眾號
總結
以上是生活随笔為你收集整理的如何用Vue实现简易的富文本编辑器,并支持Markdown语法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用React和Tailwind CSS
- 下一篇: 互联网基建成果,快速实现一个clubho