Simple-editor: 基于javascript和css开发的 Web富文本编辑器,轻量、简洁、无依赖。
Github地址
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Simple-editor demo</title>
<!-- css样式文件,webpack加载可忽略 -->
<link rel="stylesheet" href="./lib/css/editor.min.css">
</head>
<body>
<!-- 加载编辑器的容器 -->
<div id="container"> 这里写你的初始化内容 </div>
<!-- 编辑器源码文件 -->
<script type="text/javascript" src="lib/js/editor.min.js"></script>
<!-- 实例化编辑器 -->
<script type="text/javascript">
var editor = Edit.getEditor('container', {
... // 配置参数
});
</script>
</body>
</html>
因公司业务需求,硬是逼着自己写了个Web端的富文本编辑器,原本用的ueditor,但当页面渲染过多实例后,就显得有些吃力了,毕竟仅仅是压缩过的主文件都有500k,实在有些笨重,再加上项目使用中的一些个性化需求,不得不改动其大片源码,以至于不便于后期迁移和升级维护。
当然,虽然ueditor近3万行代码,显得很笨重,但其稳建的基础结构还是很值得参考的,所以最终拿了它做主要参考对象。
</br>
代码主体有四大模块:Edit.utils(工具)、Edit.ui(UI)、Edit.Editor(实例)和Edit.plugin(拓展),以及最底层的Events(事件)。
// 底层事件模块
Events = function(){}
Events.prototype = {}
// 工具类
Edit.utils = {}
// UI类
Edit.ui = {}
// ui 公用方法
Edit.ui.Stateful = {}
// ui Button 构造方法(toolbar工具按钮)
Edit.ui.Button = function(options){}
Edit.ui.Button.prototype = {}
// ui Dialog 构造方法(弹出式选项框)
Edit.ui.Dialog = function(options){}
Edit.ui.Dialog.prototype = {}
// ui Popup 构造方法(次级弹出层)
Edit.Popup = function(options){}
Edit.Popup.prototype = {}
// 拓展方法
Edit.plugin = function(){}
// 编辑器实例
Edit.Editor = function(opt){}
Edit.Editor.prototype = {}
// 创建编辑器实例
Edit.getEdItor = function(){}
// 销毁编辑器实例
Edit.delEditor = function(){}
// 注册UI
Edit.registerUI = function(){}
// 命令及UI创建
Edit.ui['bold'] = function(editor){
editor.commands['bold'] = {
execCommand: function(){
this.document.execCommand('bold', flase, null);
},
queryCommandState: function(){
return this.document.queryCommandState('bold');
}
};
var btn = new Edit.ui.Button({
name: 'bold',
className: 'eicon-bold',
title: editor.options.lang['bold'],
handles: {
click: function(){
editor.execCommand('bold');
}
}
});
editor.addListener('selectionchange', function(){
var state = editor.queryCommandState('bold');
if (!state){
btn.setChecked(false);
} else {
btn.setChecked(true);
}
});
return btn;
}
Edit.ui['xxx'] = function(editor){xxx}
......
主要结构如上,也对其某些环节进行了改良,比如DOM的字符串拼接改成了js虚拟构建,性能方面得到了提升,而且前端页面展示出来的代码也会特别干净;编辑区域方面,考虑到css的局域污染,暂时采用了iframe的嵌套方式,利用其沙盒机制可以有效防止污染(后续使用中如果发现有性能问题,会考虑去掉这种方式,全部放在当前页面操作);
编辑命令上ueditor有一套自己的封装,这里并没有采用,而是使用了Web标准的编辑API(兼容性方面或许会有些问题,待检测:不考虑远古浏览器),编辑命令主体上是放在ui回调里面注册的,只有在实例化ui的时候才会去进行注册(根据toolbar的配置去注册其对应命令,通用命令'inserthtml'除外);
</br>
// ui 注册
Edit.registerUI('button', function(editor, uiName) {
//注册按钮名称对应的command命令
editor.registerCommand(uiName, {
execCommand: function() { alert('execCommand:' + uiName) }
});
//创建一个button
var btn = new UE.ui.Button({
//按钮的名字
name: uiName,
//提示
title: uiName,
//添加额外样式,直接作用于dom元素的style属性
stlyle: 'background-image:ulr(xxx.png);background-position: -500px 0;',
//事件对象,会将对象集合依次遍历,注册在其对应的dom元素上
handles: {
click: function() {
// 这里可以不用执行命令,做自己的操作也可
editor.execCommand(uiName);
}
}
});
//当点到编辑内容上时,按钮要做的状态反射
editor.addListener('selectionchange', function() {
var state = editor.queryCommandState(uiName);
if (!state) {
btn.setChecked(false);
} else {
btn.setChecked(state);
}
});
//因为添加的是button,所以需要返回这个button
return btn;
});
UI注册基本上延续了ueditor的风格,只是属性值有些变化,回调函数中传入了实例对象,爱干嘛干嘛; 可用此方法来开发编辑器插件,比如plugins目录中的mathtype插件就是用此方法实现。
</br>
// 插件方法 拓展
Edit.plugin.register('autouplod', function(){
function getPasteImage(e){
return e.clipboardData && e.clipboardData.items && e.clipboardData.items.length == 1 && /^image\//.test(e.clipboardData.items[0].type) ? e.clipboardData.items:null;
}
function getDropImage(e){
return e.dataTransfer && e.dataTransfer.files ? e.dataTransfer.files:null;
}
function sendAndInsertFile(file,editor) {
var url = editor.options.serverBase64Url;
if (url) {
var Form = new FormData();
var loadingId = 'loading_' + (+new Date()).toString(36);
Form.append('data', file);
editor.execCommand('inserthtml', '<img id="'+ loadingId +'" src="/lib/images/loading.gif" style="max-width:100%;height:auto;">');
Edit.Ajax(url,'post',Form,function(cb){
var loader = editor.document.getElementById(loadingId);
loader.setAttribute('src',cb.data.url);
loader.removeAttribute('id');
Edit.ui.closePopup();
});
} else {
editor.execCommand('inserthtml', '<img style="max-width:100%;height:auto;" src="'+ file +'">');
}
}
return {
bindEvents:{
//插入粘贴板的图片,拖放插入图片
'ready':function(e){
var self = this;
if(window.FormData && window.FileReader) {
self.bind(self.body, 'paste drop', function(e){
var hasImg = false,
items;
//获取粘贴板文件列表或者拖放文件列表
items = e.type == 'paste' ? getPasteImage(e):getDropImage(e);
if(items){
var len = items.length,
file;
while (len--){
file = items[len];
if(file.getAsFile) file = file.getAsFile();
if(file && file.size > 0) {
hasImg = true;
var reader = new FileReader();
reader.onload = function (event) {
var base64_str = event.target.result;
sendAndInsertFile(base64_str,self);
}
reader.readAsDataURL(file);
}
}
hasImg && e.preventDefault();
}
});
//取消拖放图片时出现的文字光标位置提示
self.bind(self.body, 'dragover', function (e) {
if(e.dataTransfer.types[0] == 'Files') {
e.preventDefault();
}
});
}
}
}
}
});
插件这部分,可以用它来拓展命令和事件,上面展示的是“图片自动粘贴”。
</br>
基础介绍如上,详细的实现可能就要去看源码了,虽然有很多现成的Web编辑器可以直接使用,码界也并不提倡重复造轮子,但总是拿来主义也不太好吧,有些坑真的值得去踩一踩、填一填,不然怎好意思自居“开发者”呢,顶多算是个使用者罢了……,当然,更重要的是得满足业务需求。
</br>
(目前 Simple-editor 主文件代码1600行,压缩后的min版仅30k;好好学习,天天向上,再见!)