vue中根据光标位置添加内容块

3,312 阅读2分钟

我又来总结vue+elelmentUI项目中遇到的问题了,话不多说请看下文。

一、功能需求

  • 在一个编辑框内根据光标的位置来添加对应的内容,该内容不能重复添加
  • 添加完之后可以整体删除该内容块
  • 当然这个编辑框是可以在任意位置输入的

二、思路:

  1. 先写一个编辑框的组件,也可以不封装,看业务需求了

  2. 点击按钮时获取光标位置,如果没有光标位置则将光标位置一到文本内容的最后面

  3. 点击按钮追加内容时,要拼接一个标签如span标签,这样可以实现整体删除该内容块

  4. 拼接完之后,点击追加则获取光标的当前位置。

  5. 然后该内容就追加进去了

  6. 其实这个效果就是类似于微信聊天框输入一段文字后,选择某一个位置然后添加表情

  7. 接下来使用的api: getSelection

  8. 效果图

三、代码来了,请细细品尝

(1)组件html代码操作

<div class="cursorAppend">
    // 编辑框组件
    <EditText ref="editText" class="insertData" :contentHtml="contentHtml" @changeHtml="changeHtml"></EditText>
    // 要添加的内容
    <el-popover
        placement="left"
        :title="title"
        width="300"
        v-model="visibleObj.visibleTit"
        trigger="click"
        @hide="popoverHide">
        <span class="divider"></span>
        <div class="evtSelectItem" 
            draggable='true'
            v-for="(item, index) in conList" 
            :class="{evtConActive: evtConIndex == index}" :key="index" 
            @click="evtSelectItem(index, item)">
            {{item.contentName}}
        </div>
        <div style="text-align: right; margin: 0">
            <el-button size="mini" type="text" @click="visibleObj.visibleTit = false">取消</el-button>
            <el-button type="primary" size="mini" @click="visibleObj.visibleTit = false">确定</el-button>
        </div>
        <i slot="reference" draggable='true' @click="showAddData" class="el-icon-search taskCursor"></i>
    </el-popover>
</div>

(2)点击搜索按钮,没有光标时,使追加内容放到后面

 // 展示要追加的数据弹框
  showAddData() {
      // 点击搜索按钮,没有光标时,使追加内容放到后面
      let ele = this.editText;
      ele.focus();
      var range = document.createRange();
      range.selectNodeContents(ele);
      range.collapse(false);
      var sel = window.getSelection();
      //判断光标位置,如不需要可删除
      if(sel.anchorOffset!=0){
          return;
      };
      sel.removeAllRanges();
      sel.addRange(range);

  },

主要是下面这个方法,这是我搜索到的方法,进行了一些改动,忘了是哪个链接了,在此借用一下了。

(3)根据光标位置插入内容块,便于删除所添加的内容块

// 根据光标位置插入内容
insertHtmlAtCaret(html, str){
    let _this = this;
    let dom =  this.parseDom(html)[0];
    let element = this.editText;
    let textNode = document.createTextNode(str)
    const doc = element.ownerDocument || element.document
    const win = doc.defaultView || doc.parentWindow
    const sel = win.getSelection();
    let caretOffset = 0;
    let range;
    if (sel) {
        // IE9 and non-IE
         if (sel.getRangeAt && sel.rangeCount) {
            range = sel.getRangeAt(0); 
            range.collapse(false); 
            range.deleteContents(); 
            const preCaretRange = range.cloneRange();
            preCaretRange.selectNodeContents(element);
            let el = document.createElement("div"); 
            el.appendChild(dom)
            var frag = document.createDocumentFragment(), node, lastNode;
            while ((node = el.firstChild)) { 
                lastNode = frag.appendChild(node); 
            }
            range.insertNode(frag); 
            if (lastNode) { 
                range = range.cloneRange(); 
                let nodeHtml = document.createTextNode(element.innerText)
                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(dom);
    }
},

(4)通过下面的方法将追加的内容字符串转成数据,方便去除重复项以及传给后端,不要传带有span标签的字符串,不然很容易超出字段长度限制

getAddData(contentHtml) {
      // console.log('contentHtml', contentHtml)
      let con = [];
      if (contentHtml) {
          let list = contentHtml.split(/<span (.*?)<\/span>/g);
          list.forEach(function(e) {
              let obj = {};
              if (e.indexOf("data-json") > "-1") {
                  obj.type = "tag";
                  obj.contentCode = JSON.parse(
                      e
                          .replace(/data-json="(.*)" (.*) (.*)/g, "$1")
                          .replace(/&quot;/g, '"')
                  );
                  obj.value = e.replace(/(.*)\[(.*)\](.*)/g, "$2");
              } else if (e != "" && e != "&nbsp;") {
                  obj.type = "string";
                  obj.value = e.replace(/&nbsp;/g, "");
              }
              if (Object.keys(obj).length) {
                  con.push(obj);
              }
          });
      }
      // console.log('con',con)
      return con;
  },
  
parseDom(arg) {
  var objE = document.createElement("div");
  objE.innerHTML = arg;
  return objE.childNodes;
},

(5) 需要转一下节点类型,点击追加时是个字符串所以需要通过该方法parseDom转成node对象,不然就会报以下的错误

结语

我的表演结束,可能过程中有些许不足,希望大家多多指点。