这个本来是给 vm-manager 写的一个富文本编辑器,后来觉得独立出来维护比较方便,就单独分离出来放到NPM。之所以说人人都会写, 是因为这个组件实现起来确实比较简单,不需要很厉害的Js水平, 基本是对document.execCommand()指令的绑定。在此将过程分享给大家
预览地址 https://luosijie.github.io/vm-editor/
源码地址 https://github.com/luosijie/vm-editor
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组件, 所以写这样的一个组件,需要掌握的知识点有:
组件由 菜单部分和内容区域 2大部分组成, 其中菜单区域又由各种 指令按钮 组成,部分指令按钮还有下拉选项
指令按钮是 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>
- 将execCommand指令绑定到按钮中
- 实现点击上传按钮编译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的功能
<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>
其他的组建主要是按钮下拉菜单, 因为每个都不一样,所以要独立出来
- vm-editor-addimage: 插入图片
- vm-editor-addlink: 插入链接
- vm-editor-fontcolor: 修改颜色
因为这个富文本编辑器的开发时间比较短,没有认真研究类似优秀插件的源码 也没有 深入调研过富文本编辑器的需求。 只是参考了一些同类编辑器的实现效果和UI风格,比如simditor,然后简单实现了一下功能。
所以肯定还有很多需要改善的地方,比较明显的有:
- 浏览器的兼容
- 表格功能的实现
不管怎样,仅供大家学习使用
先这样了, 欢迎star