一文入门富文本编辑器


简介

富文本编辑器,能够使web页面像word一样,实现对文本的编辑,通常应用在一些文本处理比较多的系统中。现在业界有很多成熟的富文本编辑器,比如功能齐全啊TinyMCE、轻量高效的wangEditor、百度出品的UEditor等。富文本编辑器很多,但是却很少思考如何从零开始,实现一个富文本编辑器。本文主要简述如何从零开始,实现一个简易的富文本编辑器。

基本使用

普通的HTML标签,能够输入的通常只是表单,表单输入的是纯文本,不带格式的内容。富文本相对于表单,能够给输入文本内容增加一些自定义内容样式,比如加粗、字体颜色、背景...。富文本的实现,主要是给HTML标签,比如div增加一个contenteditable属性,拥有该属性的HTML标签,就能够对该标签里的内容,实现自定义的编辑。最简单的富文本编辑器如下:




    
    
    
    Document
     


    

基本操作

富文本类似于Word,有很多操作文本选项,比如文本的加粗、添加背景颜色、段落缩进等,使用方式是命令式的,只需要执行document.execCommand(aCommandName, aShowDefaultUI, aValueArgument),其中aCommandName是命令名称,aShowDefaultUI
一个 Boolean, 是否展示用户界面,一般为 false。Mozilla 没有实现。aValueArgument,额外参数,一般为null。

基本操作命令

以下简单列举一些富文本操作命令,下面给出一些例子的简单使用

命令 值 说明
backcolor 颜色字符串 设置文档的背景颜色
bold null 将选择的文本加粗
createlink URL字符串 将选择的文本转换成一个链接,指向指定的URL
indent null 缩进文本
copy null 将选择的文本复制到剪切板
cut null 将选择文本剪切到剪切板
inserthorizontalrule null 在插入字符处插入一个hr元素

Example:



  
    
    Hello World!
    
    
  
  
    
加粗
缩进
插入分隔符
链接百度

文本范围与选区

富文本中,文本范围和选区是一个非常强大的功能,借助于文本选区,我们可以对选中文本做一些自定义设置。核心是两个对象,Selection和Range对象。用比较官方的说法是,Selection对象,表示用户选择的文本范围或光标的当前位置,Range对象表示一个包含节点与文本节点的一部分的文档片段。简单来说,Selection是指页面中,我们鼠标选中的所有区域,Range是指页面中我们鼠标选中的单个区域,属于一对多的关系。比如,我们要获取当前页面的选区对象,可以调用var selection = window.getSelection(),如果想要获取到第一个文本选区信息,可以调用var rang = selection.getRangeAt(0),获取到选区文本信息,采用range.toString()。
文本范围与选区,一个比较经典的用法就是,富文本粘贴格式过滤。在我们往富文本编辑器中复制文本时,会保留原文本的格式,如果我们要去除复制的默认格式,只保留纯文本,该如何操作呢?
博主在处理这个问题时,首先想到的是,能不能监听粘贴事件(paste),在粘贴文本时,将剪切板内容替换掉。这一个里面也是有坑的,粘贴时操作剪切板是不生效的。在实现功能需求时,最初采用的是正则匹配,去除HTML标签。奈何文本格式五花八门,经常出现各种奇奇怪怪的字符,问题比较多,而且复制大文本时,页面存在性能问题,这并不是一种好的处理方式,直到后来真正理解了文本范围与选区,才发现这个设置,真香。
富文本选区的处理逻辑大致思路如下:

  1. 监听文本粘贴事件
  2. 阻止默认事件(阻止浏览器默认复制操作)
  3. 获取复制纯文本
  4. 获取页面文本选区
  5. 删除已选中文本选区
  6. 创建文本节点
  7. 将文本节点插入到选区中
  8. 将焦点移动到复制文本结尾

示例代码如下:

let $editArea = document.querySelector('.edit-area')
$editArea.addEventListener('paste', e => {
    // 阻止默认的复制事件
    e.preventDefault()
    let txt = ''
    let range = null
    // 获取复制的文本
    txt = e.clipboardData.getData('text/plain')
    // 获取页面文本选区
    range = window.getSelection().getRangeAt(0)
    // 删除默认选中文本
    range.deleteContents()
    // 创建一个文本节点,用于替换选区文本
    let pasteTxt = document.createTextNode(txt)
    // 插入文本节点
    range.insertNode(pasteTxt)
    // 将焦点移动到复制文本结尾
    range.collapse(false)
})

除此之外,还有很多操作可以借助于选区来实现,比如光标的定位、选中区域内容包裹其他样式等。

实现手动将光标定位到最后一个字符


function keepLastIndex(element) {
    if (element && element.focus){
        element.focus();
    } else {
        return
    }
    let range = document.createRange();
    range.selectNodeContents(element);
    range.collapse(false);
    let sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
}

选中区域包裹其他样式

function addCode () {
    let selection = window.getSelection()
    // 暂时处理第一个选区
    let range = selection.getRangeAt(0)
    // 拷贝一份原始选中数据
    let cloneNodes = range.cloneContents()
    // 移除选区
    range.deleteContents()
    // 创建内容容器
    let codeContainer = document.createElement('code')
    codeContainer.appendChild(cloneNodes)
    // 往选区内添加文本
    range.insertNode(codeContainer)
}

附件

以下为测试代码



  
    
    Hello World!
    
    
    
  
  
    
加粗
缩进
插入分隔符
链接百度
code

参考资料

  • Document.execCommand
  • Selection
  • Range