1

小鱼大大的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;
  };

好了 正式流程为:

  1. 设置div contenteditable
  2. 获取选择的文字 doc.getSelection();
  3. 初始化 this.actions() 生成内置处理函数this._actions
  4. 初始化 this.toolbar() 生成 this._menu 插入dom 调用 menu() 显示出来
  5. 如果 resize scroll 都将重新绘制 toolbar的位置
  6. mouseup keyup 鼠标和键盘选择完毕之后 触发 toolbar的显示 [根据是否有选择内容判断]
  7. 点击toolbar上任意功能 触发 this._actions highligh
  8. highlight是个复杂的过程 需要根据选择文字父元素的标签内容 判断其所拥应该被高亮的状态
  9. 最后的核心 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;');
  };

tcdona
461 声望32 粉丝

may the money keep with you —— monkey