实现一个文本框,既可输入,有可插入标签。TextArea显然是不可的,它是文本输入框,想添加dom是不可以的,于是解决思路就想到了,给div添加属性contentEditable,变成可输入,实现一个编辑器。
实现步骤:
1,首先点击插入,就要先确认光标所在的位置,就用到了 window.getSelection();
返回一个 Selection 对象,表示用户选择的文本范围或光标的当前位置。
var sel= = window.getSelection();
2,再使用 Selection 对象的 getRangeAt 方法获取 Range对象,传入参数相当于范围,传入0,相 当于没选中位置,也就相当于获取到了光标位置。
Range
接口表示一个包含节点与文本节点的一部分的文档片段,这样我们就可拿到当前光标的 位置以及对应的dom
var range = sel?.getRangeAt(0);
3,接下来就是插入内容,可以理解为替换,range对象为选中的内容,替换成你要插入的。先删 除所选中的内容:
range?.deleteContents();
4,创建索要插入的节点(我这里是用input创建了button,根据需求创建dom即可):
var p = document.createElement('input');
p.type = 'button';
p.value = text;
p.disabled = false;
p.className = styles.butTag;//react 添加类名
5,将创建的节点插入到光标或者选中的位置:
range?.insertNode(p);
这样就完成了在光标位置插入或者选中内容替换。
点击插入后会发现,插入的内容为全选状态,我们需要将Range对象所选中的内容清空,可以使用sel.removeAllRanges();取消全选。
PS:
上述的是只要是在光标位置就可点击插入,如果在一个页面中,只在某div中点击添加,在创建range对象后就要进行屏蔽处理,只能获取到dom对象,根据类名判断:
range.commonAncestorContainer?.className
这个可以获取到类名。但是如果div中已经输入了字符,或者在字符之间点击插入,会发现range.commonAncestorContainer返回的已经不是dom而是前面输入的字符串,所以需要子再进行判断他的父元素。
最后附上完整代码:
let bool = false;
let nodeItem: any; //插入的目标div
// 点击插入
const creatNodeClick = (text: string) => {
var sel: any = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
var range = sel?.getRangeAt(0);
//此处是根据类名做判断逻辑
if (range.commonAncestorContainer?.className) {
bool = range.commonAncestorContainer.className?.indexOf('textDiv') > -1;
nodeItem = range.commonAncestorContainer;
} else {
getParent(range.commonAncestorContainer);
}
if (bool) {
range?.deleteContents();
var p = document.createElement('input');
p.type = 'button';
p.value = text;
p.disabled = false;
p.className = styles.butTag;
range?.insertNode(p);
sel.removeAllRanges();
//此处是数据做了绑定,触发的onChange事件
nodeItem?.innerHTML && handleChangeText(nodeItem.innerHTML);
}
}
};
//递归查询父元素的类名
const getParent = (node: any) => {
if (node.parentNode) {
if (node.parentNode.className?.indexOf('textDiv') > -1) {
bool = true;
nodeItem = node.parentNode;
} else {
getParent(node.parentNode);
}
} else {
bool = false;
}
};
再使用contentEditable实现可输入div还会遇到很多问题需要解决:
1,换行时出现<div></br></div>,所以在监听数据变化时,需要用正则处理。(尤其是需要统计字数)
2,插入节点后,文本框中的内容包含了创建的HTML标签,所以也需要用正则将其替换掉。
3,再删除插入的节点时,不会触发所绑定的监听函数,需要再组件初始化时,添加键盘监听事件,再将内容作为参数传到onChange()中:
//初始化时,添加事件
dom.addEventListener('keydown', (e: any) => this.handleKeyDownEvent(e));
handleKeyDownEvent = (e: any) = > {
if (e.code == 'Backspace') {
// 此处需要一个延迟,不然拿到的是删除之前的数据
setTimeout(() => {
this.props.onChange(e.target.innerHTML);
}, 200);
}
}