近期项目中需要开发一个兼容PC和移动端的富文本编辑器,其中包含了一些特殊的定制功能。考察了下现有的js富文本编辑器,桌面端的很多,移动端的几乎没有。桌面端以UEditor为代表。但是我们并不打算考虑兼容性,所以没有必要采用UEditor这么重的插件。为此决定自研一个富文本编辑器。本文,主要介绍如何实现富文本编辑器,和解决一些不同浏览器和设备之间的bug。
准备阶段
在现代浏览器中已经为我们准备好了许多API来让 html 支持富文本编辑功能,我们没有必要自己完成全部内容。
contenteditable=”true”
首先我们需要让一个 div 成为可编辑状态,加入 contenteditable="true" 属性即可。
<div contenteditable="true" id="rich-editor"></div>
在这样的 <div> 中插入任何节点都将默认是可编辑状态的。如果想插入不可编辑的节点,我们就需要指定插入节点的属性为 contenteditable="false" 。
光标操作
作为富文本编辑器,开发者需要有能力控制光标的各种状态信息,位置信息等。浏览器提供了 selection 对象和 range 对象来操作光标。
selection 对象
Selection对象表示用户选择的文本范围或插入符号的当前位置。它代表页面中的文本选区,可能横跨多个元素。文本选区由用户拖拽鼠标经过文字而产生。
获得一个 selection 对象
let selection = window.getSelection();
通常情况下我们不会直接操作 selection 对象,而是需要操作用 seleciton 对象所对应的用户选择的 ranges (区域),俗称”拖蓝“。获取方式如下:
let range = selection.getRangeAt(0);
由于浏览器当前可能存在多个文本选取,所以 getRangeAt 函数接受一个索引值。在富文本编辑其中,我们不考虑多选取的可能性。
selection 对象还有两个重要的方法, addRange 和 removeAllRanges 。分别用于向当前选取添加一个 range 对象和 删除所有 range 对象。之后你会看到他们的用途。
range 对象
通过 selection 对象获得的 range 对象才是我们操作光标的重点。Range表示包含节点和部分文本节点的文档片段。初见 range 对象你有可能会感到陌生又熟悉,在哪儿看见过呢?作为一个前端工程师,想必你一定拜读过《javascript 高级程序设计第三版》 这本书。在第12.4节,作者为我们介绍了 DOM2 级提供的 range 接口,用来更好的控制页面。反正我当时看的一脸????这个有啥用,也没有这种需求啊。这里我们就大量的用到这个对象。对于下面节点:
<div contenteditable="true" id="rich-editor">
<p>百度EUX团队</p>
</div>
光标位置如图所示:
打印出此时的 range 对象:
其中属性含义如下:
* startContainer: range 范围的起始节点。
* endContainer: range 范围的结束节点
* startOffset: range 起点位置的偏移量。
* endOffset: range 终点位置的偏移量。
* commonAncestorContainer: 返回包含 startContainer 和 endContainer 的最深的节点。
* collapsed: 返回一个用于判断 Range 起始位置和终止位置是否相同的布尔值。
这里我们的 startContainer , endContainer, commonAncestorContainer都为 #text 文本节点 ‘百度EUX团队’。因为光标在‘度‘字后面,所以startOffset 和 endOffset 均为 2。且没有产生拖蓝,所以 collapsed 的值为 true。我们再看一个产生拖蓝的例子:
光标位置如图所示:
打印出此时的 range 对象:
由于产生了拖蓝 startContainer 和 endContainer 不再一致,collapsed 的值变为了 false。startOffset 和 endOffset 正好代表了拖蓝的起终位置。更多的效果大家自己尝试吧。
操作一个 range 节点,主要有如下方法:
setStart(): 设置 Range 的起点
setEnd(): 设置 Range 的终点
selectNode(): 设定一个包含节点和节点内容的 Range
collapse(): 向指定端点折叠该 Range
insertNode(): 在 Range 的起点处插入节点。
cloneRange(): 返回拥有和原 Range 相同端点的克隆 Range 对象
富文本编辑里面常用的就这么多,还有很多方法就不列举了。
修改光标位置
我们可以通过调用 setStart() 和 setEnd() 方法,来修改一个光标的位置或拖蓝范围。这两个方法接受的参数为各自的起终节点和偏移量。例如我想让光标位置到”百度EUX团队”最末尾,那么可以采用如下方法:
let range = window.getSelection().getRangeAt(0),
textEle = range.commonAncestorContainer;
range.setStart(range.startContainer, textEle.length);
range.setEnd(range.endContainer, textEle.length);