一块儿动手撸一个富文本编辑器吧

前言

公司要作一个笔记模块,须要用到富文本编辑器。以前有耳闻富文本编辑器是天坑。知乎-为何说富文本编辑器是个天坑? 在试过了市面上主流的编辑器后,发现或多或少都不符合要求。主要有如下问题:javascript

  1. CKEditor功能很强大,可是太复杂,有不少用不到的地方。
  2. 项目前端框架是Vue,最好是基于Vue2.x的编辑器
  3. 网上开源的编辑器体验或多或少有不知足的地方。

还好开发时间比较富足,因而决定在vue-html5-editor基础上二次开发,最后完成上线的做品,呼唤star✨ 🙋 Github:my-vue-editorhtml

实现套路

web端实现富文本编辑器主要有2个套路:前端

  1. 利用contenteditable属性结合document.execCommand API实现,好比国外的CKEditor、百度的UEditor、优秀的后起之秀wangEditor。
  2. 彻底本身模拟实现selection、视图渲染等一切。好比Google Doc、有道云笔记、基于electron开发的VS Code。

这里咱们很理智的选择了第一种实现方式。先简单介绍下编辑器很重要的几个概念:vue

Range/Selection

Range 翻译过来是范围,幅度的意思,与数学上的“区间”这以概念相似。浏览器提供的Range对象用来描述DOM树中的一段连续的范围。html5

startContainerstartOffset描述Range的起始处,endContainerendOffset描述Range的结尾处。当一个Range的起始处和结尾处是同一个位置时,该Range就处于collapsed状态。java

Selection(选区)管理整个页面当前的RangeRange的绘制。当Selection中的Range处于collapsed状态时,便是平常所说的光标。光标实际上是Selection的一种特殊状态。node

document.execCommand

浏览器原生为咱们提供了一些对Range内节点进行富文本操做的方法,这些方法都是经过document.execCommand调用。git

bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)
复制代码

好比github

// 向当前插入点插入一个p标签。
document.execCommand('insertHTML', false, '<p></p>')

// 将框选部分字体变为绿色,若是是collapsep状态则接下来输入的文字为绿色
document.execCommand('foreColor', false, '#00ff00')
复制代码

咱们编辑器的架都是围绕这两个概念展开的:web

  1. 当咱们点击编辑器各类功能按钮(好比插入图片、加粗、下划线)时内容区域会失去焦点,因此咱们须要一种可以保存当前Range对象,并在须要时能够调用的机制。
  2. 咱们已经知道document.execCommand是用来操纵选区HTML结构的,可是原生提供的方法的逻辑大多数都不彻底符合咱们的须要,或者存在兼容性问题。因此咱们封装咱们本身的构造函数Command用来操纵富文本,不一样的按钮点击后就会实例化相应的Command并执行相关操做。

对于第一点,只须要定义一个保存,一个设置方法。

// 保存当前Range
function saveCurrentRange () {
    // 获取selection对象
    const selection = window.getSelection ? window.getSelection() : document.getSelection()
      if (!selection.rangeCount) {
        return
      }
      const content = this.$refs.content
      for (let i = 0; i < selection.rangeCount; i++) {
        // 从selection中获取第一个Range对象
        const range = selection.getRangeAt(0)
        let start = range.startContainer
        let end = range.endContainer
        // 兼容IE11 node.contains(textNode) 永远 return false的bug
        start = start.nodeType === Node.TEXT_NODE ? start.parentNode : start
        end = end.nodeType === Node.TEXT_NODE ? end.parentNode : end
        if (content.contains(start) && content.contains(end)) {
        // Range对象被保存在this.range 
          this.range = range
          break
        }
      }
}

// 设置Range对象
function restoreSelection () {
    // 首先获取selection对象并清除当前的Range
      const selection = window.getSelection ? window.getSelection() : document.getSelection()
      selection.removeAllRanges()
      // 从this.range中得到保存的Range设置为Selection的Range对象
      if (this.range) {
        selection.addRange(this.range)
      } else {
        // 若是以前没有保存Range则新建一个
        const content = this.$refs.content
        const row = RH.prototype.newRow({br: true})
        const range = document.createRange()
        content.appendChild(row)
        range.setStart(row, 0)
        range.setEnd(row, 0)
        selection.addRange(range)
        this.range = range
      }
    }
复制代码

有了这两个方法,咱们只须要为编辑器的内容区域注册mouseup keyup mouseout事件监听来实时执行saveCurrentRange,当点击按钮后在实例化Command前执行restoreSelection

对于第二点,封装execCommand方法很好理解,好比我要实现"缩进indent"的功能,document.execCommand 就提供了indent这个参数能够直接使用,当Range处于ul>li,中执行indent会让ul嵌套ul,变成ul>ul>li,多个缩进就执行多个嵌套。这知足咱们的须要。

// 缩进前
<ul>
    <li>当前光标位置</li>
</ul>

// 缩进后
<ul>
    <ul>
        <li>当前光标位置</li>
    </ul>
</ul>
复制代码

可是当Range处于通常的块级元素中,执行indent会让块级元素外面嵌套blockquote元素,咱们想经过在块级元素上增长margin-left来处理通常块级元素的缩进。

// 缩进前
<p>当前光标位置</p>

// 缩进后
<blockquote>
    <p>当前光标位置</p>
</blockquote>

// 咱们但愿的状况
<p style='margin-left: 8%;'>当前光标位置</p>
复制代码

咱们只须要封装execCommand方法,当其参数为indent时,执行对应封装好的indent方法,判断Range是处于列表元素仍是其余块级元素中分别对待就行。 这里之因此要采用构造函数而不是普通函数的形式,是由于全部原生的execCommand方法,当执行时浏览器内部会对该contenteditable区域维护一个undo栈和一个redo栈,使得每个修改行为能够撤销和重作。

咱们封装的方法覆写了原生的方法,就会破坏undo/redo栈的连续性,致使撤销和重作出错或失效。因此咱们须要在每一个Command实例上保存执行前编辑器区域的DOM结构(快照)和执行后编辑器区域的DOM结构(快照),并把这个实例推入相应的undo/redo栈。当咱们执行撤销和重作操做时只须要从相应的栈中取出保存的快照恢复到内容区域便可。 因此你发现啦,undoredo也是两个须要重写的Command

到这里一个富文本编辑器的雏形就出来了,咱们只须要在这个基础上不断完善咱们的Command,再处理须要过滤的样式、多端数据结构同步、各类浏览器的兼容性等一个又一个坑就能作出功能丰富的编辑器啦。👏👏👏😄

都看到这里啦,来试试咱们的编辑器吧,Github:my-vue-editor 以为好用给个star呗老铁

相关文章
相关标签/搜索