使用vue编写的,没有任何依赖,可改写其它形式
轻量级编辑器只是在document.execCommand()方法做了包装,不兼容的浏览器器生成的标签是一致的,所以的富文本的选择要根据项目决定
实现了一点功能,写了个dome,加深对富文本原理的理解
template
<div class="page-editor">
<div class="editor-wrapper">
<div class="toolbar">
<ul @click="menuHandle">
<li v-for="(item,index) in menuList" :key="index" :data-index="index">
<a href="javascript:;" :class="[{'ban-operate': item.isActive}]">
<i :class="['iconfont',item.icon]"></i>
</a>
</li>
</ul>
</div>
<div class="body" contenteditable="true" ref="editor" v-focus="true"
@keyup.enter="enterHandle"
@keyup.delete="deleteHandle"
>
</div>
</div>
</div>
js
template: tpl,
data() {
return {
newNode:null,
lastRange:null,
selection:window.getSelection(),
isIE:window.navigator.userAgent.indexOf("MSIE")>=1,
menuList:[
{
icon:"icon-zitibiaoti",
detail:"标题",
isActive:false
},
{
icon:"icon-jiacu",
detail:"加粗",
isActive:false
},
{
icon:"icon-xieti",
detail:"倾斜",
isActive:false
},
{
icon:"icon-youxuliebiao",
detail:"有序列表",
isActive:false
}
]
}
},
created(){
},
mounted(){
this.initEditor();
},
methods:{
initEditor(){
this.$refs.editor.innerHTML = "<p><br/></p>"
},
enterHandle(){
let range = this.getRange();
let el = this.getNode(range.commonAncestorContainer);
if(range.collapsed) {
if (el.nodeName === "DIV") {
let p = document.createElement("p");
p.innerHTML="<br>";
el.parentNode.replaceChild(p,el);
};
}
},
deleteHandle(){
const that = this;
if(!that.$refs.editor.innerHTML){
that.$refs.editor.innerHTML = "<p><br/></p>"
}
},
menuHandle(el){
let target = this.getTarget(el,"LI"),
index = this.dataSet(target,"index");
switch (index) {
case 0:
this.menuList[index].isActive= !this.menuList[index].isActive;
this.titleBtn(this.menuList[index].isActive);
break;
case 1:
this.menuList[index].isActive= !this.menuList[index].isActive;
this.blodBtn(this.menuList[index].isActive);
break;
case 2:
this.menuList[index].isActive= !this.menuList[index].isActive;
this.italicBtn(this.menuList[index].isActive);
break;
case 3:
this.menuList[index].isActive= !this.menuList[index].isActive;
this.olListBtn(this.menuList[index].isActive);
break;
}
},
titleBtn(isFouce) {
console.log(isFouce);
if(isFouce){
let insertHtml = "<span style='font-size:28px;'></span>";
this.addHtml(insertHtml);
}else{
this.clearForm();
}
},
blodBtn(isFouce){
if(isFouce) {
let insertHtml = "<span style='font-weight:600;'></span>";
this.addHtml(insertHtml);
}else{
this.clearForm();
}
},
italicBtn(isFouce){
if(isFouce) {
let insertHtml = "<i></i>";
this.addHtml(insertHtml);
}else{
this.clearForm();
}
},
olListBtn(isFouce){
if(isFouce) {
let range = this.getRange();
document.execCommand("insertOrderedList");
if (range.collapsed) {
let el = range.commonAncestorContainer;
while (el.nodeName !== "P") {
el = el.parentNode;
}
;
let replaceNode = el.firstChild.cloneNode(true);
el.parentNode.replaceChild(replaceNode, el);
}
this.clearRange(range);
}else{
this.clearForm();
}
},
clearForm(){
let range = this.getRange();
let pNode = range.endContainer.parentNode.parentNode;
if(pNode){
let str = pNode.innerHTML.replace(/\u200B/g,'');
range.selectNode(pNode); //创建范围的内容
range.extractContents();
let fNode = document.createDocumentFragment();
let newNode = document.createElement(pNode.nodeName);
if(pNode.attributes['style']){
let styles = pNode.attributes['style'].value;
newNode.style= styles;
}
fNode.appendChild(newNode);
if(str.indexOf("<br>")>-1){
str = str.replace(/\<br>/g,'');
}
newNode.innerHTML = str+'\u200B <br>';
range.insertNode(fNode);
};
range.setEndAfter(range.endContainer.parentNode);
range.collapse(true);
},
getHtml(){
return this.$refs.editor.innerHTML;
},
setHtml(val){
this.$refs.editor.innerHTML = val;
},
clearHtml(){
this.$refs.editor.innerHTML="<p><br></p>";
},
getRange(){
if(this.selection.rangeCount>0){
return this.selection.getRangeAt(0);
};
},
addHtml(html){
let range = this.getRange();
let fragment = range.createContextualFragment(html);
let node = fragment.firstChild;
range.insertNode(fragment);
if(!node.firstChild){
let text = document.createTextNode("\u200B");
node.appendChild(text)
};
range.setStart(node.firstChild,1);
range.setEnd(node.firstChild,1);
range.collapse(true);
this.clearRange(range);
},
clearRange(range){
this.lastRange = range.cloneRange();
range.detach();
range = null;
this.selection.removeAllRanges();
this.selection.addRange(this.lastRange);
},
dataSet(el,name){
let index;
if(el){
if (el.dataset) {
index = parseInt(el.dataset[name]);
} else {
index = parseInt(el.getAttribute('data-'+name));
}
return parseInt(index);
}
},
getTarget(el,targetName) {
let target = el.target;
while(target.nodeName!==targetName){
target = target.parentNode;
};
el.stopPropagation();
return target;
},
getNode(el){
while(el.nodeType!==1){
el = el.parentNode;
};
return el;
}
},
directives: {
focus: {
inserted: function (el,binding) {
if(binding.value){
el.focus();
}
}
}
}
less
.page-editor{
position: relative;
border: 1px solid #c9d8db;
margin: 15px;
.editor-wrapper{
position: relative;
background: #fff;
}
.toolbar{
border-bottom: 1px solid #eee;
background: #fff;
ul li{
position: relative;
display: inline-block;
font-size: 0;
}
ul li a{
display: inline-block;
width: 46px;
height: 40px;
outline: 0;
color: #333;
font-size: 14px;
line-height: 40px;
vertical-align: middle;
text-align: center;
text-decoration: none;
}
ul li a i{
font-size: 18px;
}
}
.body{
font-size: 16px;
font-family: arial,sans-serif;
line-height: 1.6;
color: #333;
outline: 0;
word-wrap: break-word;
padding: 22px 15px 40px;
min-height: 300px;
cursor: text;
position: relative;
z-index: 1;
background: 0 0;
}
.body{
ol{
margin-left: 15px;
}
}
.ban-operate{
background-color: #909399;
}
}