在普通的 textarea 中,只能显示普通的文本。如果简单的输入文本,textarea 便足以胜任。但是实际情况往往要复杂得多。

简单版本的插入表情

常见的版本一般都是使用 textarea,然后表情使用某种约定的文本格式代替,比如“你好啊[微笑]”。在呈现的时候,通过固定的文本解析方法将内容中的表情文本替换成图片。新浪微博中发微博的输入框就是如此。但是,在这有一点需要注意,如果只是简单的在文本的最后插入表情之类的预定好的文本格式,只需获取到到 textarea 的 value 然后做加法即可。

1
2
let editor = document.querySelect('#editor');
editor.value += '[微笑]';

没你想的这么简单

但实际情况却没有这么简单,因为用户可以自己选择光标的位置。当用户在某一段文本中间插入光标之后,可不是简单的加法了。在这种情况下,需要获取到光标所在位置,在这个位置上插入用来代替表情的文本,然后将光标设置到表情文本的后面。在这需要两个额外的方法:getCaretPositionsetCaretPosition

getCaretPosition/setCaretPosition

浏览器并没有提供直接获取光标位置的方法,需要我们变通的处理。浏览器基本上都支持文本框的select()方法,这个方法用于选中文本框中所有的文本,但是只能乖乖的拿到返回的所有文本。HTML5 添加了两个属性:selectionStart 和 selectionEnd 帮助我们更加顺利地获取选择的文本。这两个属性中保存的是基于0的数值,表示所选择的文本的范围,分别表示文本选区(选中的文本)开头和结尾相对整个文本内容的偏移量(在整个文本内容中的位置)。例如:

1
2
3
4
5
6
7
8
let editor = document.querySelector('#editor');
// 从第一个字符开始,选中三个字符
editor.selectionStart = 0;
editor.selectionEnd = 1;

// 从第三个字符开始选中三个字符
editor.selectionStart = 2;
editor.selectionEnd = 5;

说到这你可能要问了,这个光标有啥关系啊?别急,听我慢慢说。既然上述两个设置不同数字可以选择文本,那如果两个值设置成相同的数字,会怎么样呢?

1
2
3
// 从第三个字符开始选中零个字符
editor.selectionStart = 2;
editor.selectionEnd = 2;

起点和终点重合了!那么换个角度来描述就是:当我们在获取光标位置的时候,其实就是选中的文本范围起点和重点重合,相当于文本范围的起点偏移量其实就是光标所在的位置偏移量,所以此时selectionStart的返回值就是我们需要的结果。

更关键的是,当 End 和 Start 设置成相同值时,选区也是空的,起点和重点充电,就好像是设置了光标的位置。其实有一个简便的方法 setSelectionRange(start, end),原理相同。

当然有兴趣你也可以试试 End小于 Start的情况。上述这些在现代浏览器和 IE9+ 上都支持。

前端向来麻烦的还是浏览器的兼容问题。在低版本的 IE 中只能使用 document.selection 对象来模拟光标定位了。document.selection 只存在于 IE8 及更早的版本(可以使用 window.getSelection 代替),保存着用户在整个文档范围内选择的文本信息,但是无法确定用户选择的是页面中哪个部位的文本。要想取得选择的文本,首先需要创建一个范围(Range,IE9+ 支持 DOM Range API,但是 IE8及之前的版本不支持,但是有类似的概念,text range。这是 IE 专有的特性)。可使用 document.selection.createTextRange 来创建我们所需要的 text range。然后利用moveStart().aspx)将文本的范围的起点从当前位置(当前位置起点和重点是重合的)移动到文本的开头,然后计算选中文本的长度,这个长度值可以用来代替当前光标的位置。

1
2
3
let range = document.selection.createRange();
range.moveStart("character", editor.value.length);
cursurPosition = range.text.length;

设置光标位置思路类似,但是代码稍有不同:

1
2
3
4
5
let range = editor.createTextRange();
range.collapse(true);
range.moveEnd('character', pos);
range.moveStart('character', pos);
range.select();

总的来说,在 textarea 中获取和设置光标位置还是蛮简单的。讲到这里了,我想插入表情应该是很轻松的一件事情了

1
获取光标位置(文本范围前后重叠) -> 修改文本范围(或者手动拼接) -> 重新设置光标位置

至此,表情插入功能的基本实现。

还没结束

上述例子中,在输入框中表情只能以文本的形式呈现。如果想在输入框中呈现输入的表情,该怎么办呢?使用 contenteditable 属性为 true 的容器代替 textarea 是必须的,因为 textarea 中只能显示文本。但是这就足够了吗?不,显然不够。没有了 textarea 则以为这没有了 setSelectionRange, selectionStart 和 selectionEnd。但是好在原理也是类似,依旧使用 Range API 或者 Text Range(IE8及其更低版本)。具体的可以参考这篇:html元素contenteditable属性如何定位光标和设置光标和这篇在可编辑的div中插入图片。 具体实现代码我就不贴了,大家可以自己思考捋一捋。举一反三,如果你真真正正地知道如何正确插入图片,那么插入复杂的 DOM 结构对你来说也是轻而易举。