小鱼大大的Pen好像很多粉的样子,这几天看了看,学习了一下
编辑的原理依赖 div 上的 contenteditable 属性
开启和关闭功能是下面的destroy rebuild函数
Pen.prototype.destroy = function(isAJoke) {
var destroy = isAJoke ? false : true
, attr = isAJoke ? 'setAttribute' : 'removeAttribute';
if(!isAJoke) {
this._sel.removeAllRanges();
this._menu.style.display = 'none';
}
this._isDestroyed = destroy;
this.config.editor[attr]('contenteditable', '');
return this;
};
Pen.prototype.rebuild = function() {
return this.destroy('it\'s a joke');
};
如果是低端浏览器,兼容方案为设置div.pen.innerHTML = 一个textarea
stay功能的实现
window.onbeforeunload = function() {
if(!that._isDestroyed) return 'Are you going to leave here?';
};
menu函数使得选中文字后 toolbar 居中显示
// show menu
Pen.prototype.menu = function() {
var offset = this._range.getBoundingClientRect()
, top = offset.top - 10
, left = offset.left + (offset.width / 2)
, menu = this._menu;
// display block to caculate it's width & height
menu.style.display = 'block';
menu.style.top = top - menu.clientHeight + 'px';
menu.style.left = left - (menu.clientWidth/2) + 'px';
return this;
};
好了 正式流程为:
- 设置div contenteditable
- 获取选择的文字 doc.getSelection();
- 初始化 this.actions() 生成内置处理函数this._actions
- 初始化 this.toolbar() 生成 this._menu 插入dom 调用 menu() 显示出来
- 如果 resize scroll 都将重新绘制 toolbar的位置
- mouseup keyup 鼠标和键盘选择完毕之后 触发 toolbar的显示 [根据是否有选择内容判断]
- 点击toolbar上任意功能 触发 this._actions highligh
- highlight是个复杂的过程 需要根据选择文字父元素的标签内容 判断其所拥应该被高亮的状态
- 最后的核心 this._actions来了 行 块 形式判断不同的操作目标节点 然后执行 document.execCommand命令
当然他还有markdown插件
1. 每次 keypress 的时候会触发 parse
2. parse 对输入push到数组covertor.stack中
3. 判断为空格按下 则把 stack中的命令取出 执行valid
4. valid通过的,则进入 action环节 渲染输出不同的格式
写的非常粗糙,主要为了记录一下流程
还有我觉得Pen大量的时间精力都在处理toolbar highlight和节点查找 判断上
虽然短短300行,依然提供了较好的体验.源码值得一看
附上简短的3个util :D
// type detect
utils.is = function(obj, type) {
return Object.prototype.toString.call(obj).slice(8, -1) === type;
};
// 判断类型
// copy props from a obj
utils.copy = function(defaults, source) {
for(var p in source) {
if(source.hasOwnProperty(p)) {
var val = source[p];
defaults[p] = this.is(val, 'Object') ? this.copy({}, val) :
this.is(val, 'Array') ? this.copy([], val) : val;
}
// 深层复制
}
return defaults;
};
// log
utils.log = function(message, force) {
if(window._pen_debug_mode_on || force) console.log('%cPEN DEBUGGER: %c' + message, 'font-family:arial,sans-serif;color:#1abf89;line-height:2em;', 'font-family:cursor,monospace;color:#333;');
};