需求
需求是这样的,有一个输入框需要输入特殊的变量,后续使用时动态替换变量类容
这是一个模板输入{变量1},其他输入{变量2}
输入可以用一个变量可以使用下拉框选择,像这样
但是这样体验并不好,变量可以删除一部分,而且没有高亮
所以想要这样的效果
实现原理
实现原理就是使用富文本输入框,变量使用contenteditable="false"元素,这样就不可以删除一部分内容了
<div contenteditable="true" class="ant-input template-input">
这是一个模板输入<span class="replace_label" contenteditable="false" tpl_val="$1$">变量1</span>,
其他输入<span class="replace_label" contenteditable="false" tpl_val="$2$">变量2</span>
</div>
看看效果
实现技术点
1.在光标处插入文本
2.处理光标位置
实现编辑器第一个问题就是需要处理选区和光标,我们这个需求比较简单,基本上处理光标问题即可
需要用到Selection 对象和 Range 对象
Selection 对象表示用户选择的文本范围或插入符号的当前位置。
Selection 对象所对应的是用户所选择的 ranges(区域)就是 Range(框选变蓝的区域), 该接口表示一个包含节点与文本节点的一部分的文档片段。
Selection 和Range的区别
Selection 可能包含多个Range,但通常我们只处理一个Range的区别,
Selection 主要可以处理焦点,即光标位置,Range处理选择的内容
在光标处插入文本
- 查找光标位置
- 插入内容
- 光标定位到插入位置后
主要用到的方法
Selection.getRangeAt(0) 获取Range
Selection.anchorNode 只读属性返回选区开始位置所属的节点(可以判断是否有选取)
Selection.collapseToEnd() 折叠选区结尾,如果是可编辑区域,会获取焦点,显示光标
Range.selectNode() 选择节点
代码示例:
let selection = window.getSelection();
let range = null;
if(selection.anchorNode){
range = selection.getRangeAt(0);
range.deleteContents();
}else{
range = document.createRange();
let ipt_node = this.$refs.edit_ipt;
range.selectNodeContents(ipt_node);
range.collapse();
}
let textNodes = this.replaceVal(val);
let last = textNodes.lastChild;
range.insertNode(textNodes);
range.selectNode(last);
selection.removeAllRanges();
selection.addRange(range);
selection.collapseToEnd();
处理光标位置
对于contenteditable="false"元素,有可能遇到这种情况,就光标在输入框外面了,而不是在后面
这种情况一般是后面没有内容的时候就会出问题,所以我们在后面放一个特殊符号
这是一个0宽字符,不占宽度的
但是多了一个符号,删除时候要删2次,而且删了那个字符,也会出现光标问题,所以我们要处理删除的时候,同时删掉2个,就是判断光标位置的前一个字符是否是特殊字符,并且前面一个是我们的特殊元素,就一起删除
需要用到的方法
Range.endContainer 返回包含 Range 终点的节点。
Range.endOffset 返回代表 Range 结束位置在 Range.endContainer 中的偏移值的数字。
如果 endContainer 的 Node 类型为 Text, Comment,或 CDATASection,偏移值是 endContainer 节点开头到 Range 末尾的总字符个数。对其他类型的 Node , endOffset 指 endContainer 开头到 Range 末尾的总 Node 个数
代码示例:
onKeydown(e){
if(e.key == 'Backspace'){
let selection = window.getSelection();
const removeReplace = (last_child) => {
//有可能有空的文本节点
if(last_child.wholeText.length == 0){
let last_child_prev = last_child.previousSibling;
last_child.remove();
last_child = last_child_prev;
}
if(last_child.wholeText.length == 1 && last_child.wholeText.charCodeAt(0) == 65279){
let previousSibling = last_child.previousSibling;
console.log(previousSibling);
if(previousSibling && previousSibling.nodeType == Node.ELEMENT_NODE ){
if(previousSibling.classList && previousSibling.classList.contains('replace_label')){
previousSibling.remove();
last_child.remove();
return true;
}
}
}
}
if(selection.anchorNode){
let range = selection.getRangeAt(0);
console.log(range);
if(range.collapsed){
let endOffset = range.endOffset;
if(endOffset == 0){
endOffset = 1;
}
if(range.endContainer.nodeType == Node.TEXT_NODE){
//判断删除特殊符号
if(removeReplace(range.endContainer,range)) {
this.replaceToText()
e.preventDefault();
}
}else{
let last_child = range.endContainer.childNodes[endOffset-1];
//判断删除特殊符号
if(last_child.nodeType == Node.TEXT_NODE){
if(removeReplace(last_child)){
this.replaceToText()
e.preventDefault();
}
}
}
}
}
}
}
总结
对于富文本编辑器来说,需要处理的问题非常多,我们这么简单的需求,光标问题处理起来也比较麻烦