模拟可编辑div输入域同时输入文字表情
近期遇到了模拟可编辑div输入域同时输入文字表情的需求,本来还觉得很好做,但是在具体实施的过程中遇到了一点问题。
第一个比较简单的问题是表情和对应字符的映射关系,这部分比较好做,没有用富文本框也没有用编辑器,做了表情和相应字符的对应关系只有就可以实现这个需求。
第二个问题在解决过程中就比较棘手了,因为是自己模拟输入域,所以对于在文字中加入表情后光标的定位及后续的输入是有要求的,就是得符合正常的输入习惯。这个问题的核心就是对于node节点的操作和光标对象的熟悉程度及其内部属性和方法的使用。对于前端开发者来说其实是很基础的一个知识点(此处羞愧脸)。不熟悉的同学其实也不要有心里负担,其实本来这写知识点及其使用网上一大推,但是不一定能够满足自己要的需求。对于这第二个问题来说,我的需求就是,在任何地方都可以做到想插入文字一样插入表情,而且光标像插入文字一样定位到表情后面。这么说浅显易懂吧。还不懂的话上图来理解一下
就是这样,将光标定位到“の”后面,插入表情,然后光标位置定位在表情后面。会的人会觉得很简单(膜拜大佬),不会的也有思路:就是监听光标位置,首先记录光标的初始位置,然后插入表情,然后计算长度,接着在定位光标。刚开始我就是这么想的,但是做了你就会发现一个问题,对于range对象来说对于非文本节点的计算光标是一个大问题。那么接下来我们来普及一下range这个对象(好开心,又认识了一个对象😂)。
这是对range的定义:
Range 对象
Range 对象表示文档的连续范围区域,如用户在浏览器窗口中用鼠标拖动选中的区域。
Selection对象
所对应的是用户所选择的 ranges
(区域),俗称拖蓝。默认情况下,该函数只针对一个区域,我们可以这样使用这个函数:
大致上来说range和selection都是区域
range里的属性等等还可以操作光标,做一些索引,位置的定位,输出等,这样你就可以在特定位置插入你想插入的节点了。
我们假设你已经做好了表情和对应字符的转换,列如对应的是[微笑]。而且我们假设你可以选取一段文字中光标的位置。接下来你讲在这个位置插入表情符号,成功了。这时候有人就说了,你这不废话么,肯定成功啊,如此清晰明确的逻辑和操作。但是如果看效果的话,你接下来插入的表情一般都不会成功。因为光标只会读取文本节点,当你插入了非文本节点。在次定位光标时,问题就在于,你插入的非文本节点会对你光标位置的输出造成干扰。也就是说不能指哪打哪。我们来看以下几种情况:
这是光标定位在1111111的第四个1之后的selection。我们可以通过focusOffset等诸多属性来在确切位置插入。完全没问题。但是注意看data属性,值是1111111,只是部分文本节点,这很符合逻辑,因为1111111和222222本来就是两个文本节点,没有normalize在一起。
接下来这种情况
输出的data,嗯,没毛病,是222222, anchorOffset是3????思考一下,你会发现,是根据当前定位节点来计算的,没毛病。那插入位置只能你自己计算喽,加上第一个文本节点,在加上第二个元素的长度,嗯,然后定位光标。居然发现报错了,这是因为把第二个元素的长度完全解析成了img标签的长度,所以,没一个文本节点的光标都是从零开始,并且非文本节点还不计算在内。经过我的努力,用这种数字的定位是解决不了的。
找了一些可以规避这个问题的方法,发现最优的可以将表情插入,并且光标的位置定位在表情后面,但是不能做到在任何位置插入表情。找了半天,找到了不同点。但是参考别人做的,和我的不一样应该是我用了一种取巧的办法。下面上代码:
function insertHtmlAtCaret(html) {
var sel, range;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
// Range.createContextualFragment() would be useful here but is
// non-standard and not supported in all browsers (IE9, for one)
var el = document.createElement("div");
el.innerHTML = html;
var frag = document.createDocumentFragment(), node, lastNode;
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node);
}
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if (document.selection && document.selection.type != "Control") {
// IE < 9
document.selection.createRange().pasteHTML(html);
}
}
$(".abc").click(function (){
// $(".abc").trigger('click');
document.getElementsByClassName('test')[0].focus(); insertHtmlAtCaret('<img src="wdwe.png">');
});
insertHtmlAtCaret方法是个黑箱,想了解内容的具体逻辑可言去学习一下。但是刚开始的时候我用这个方法也遇到了一点问题。点击的表情总是会加在内容的最前面。这里有一个注意点,那就是类名为abc的元素必须是input type='button'的元素。
希望遇到和我一样问题的人,看到了这篇文章会对你的编码有帮助,如果有不明白的欢迎一起探讨。