React:div可编辑后,点击按钮插入标签

实现一个文本框,既可输入,有可插入标签。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,换行时出现


,所以在监听数据变化时,需要用正则处理。(尤其是需要统计字数)

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);
    }
  }

你可能感兴趣的:(屠龙,react.js,javascript,前端)