这个原本是给 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
组件由 菜单部分和内容区域 2大部分组成, 其中菜单区域又由各类 指令按钮 组成,部分指令按钮还有下拉选项npm
指令按钮是 execConmand 的装载器,须要实现如下功能浏览器
<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
<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>
其余的组建主要是按钮下拉菜单, 由于每一个都不同,因此要独立出来学习
由于这个富文本编辑器的开发时间比较短,没有认真研究相似优秀插件的源码 也没有 深刻调研过富文本编辑器的需求。
只是参考了一些同类编辑器的实现效果和UI风格,好比simditor,而后简单实现了一下功能。
因此确定还有不少须要改善的地方,比较明显的有:
无论怎样,仅供你们学习使用
先这样了, 欢迎star