前端 @功能的实现,在react、vue中通用

190 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

废话不多说,先上需求:
在输入框中输入@后,弹出选人的浮框,然后选择人(可多选),选完后,关闭浮框,然后在输入框中加入@XXX,然后焦点定位刚开始输入@的位置。删除的时候,@XXX要一块删除。
分析:
1.因为要在输入框中把@xxx高亮显示,所以需要用到可编辑元素。

 <div contentEditable></div>

2.用到selection和range对象

开撸:

<div 
    className={styles.talkInput} 
    id="talkInput" 
    contentEditable 
    onKeyDown={onTalkKeyDown}
    onInput={changeTalkContent}> 
</div>
function changeTalkContent(e) {
    setTalkContent(e.target.innerText);
    if (e.nativeEvent.data === '@') { //监听输入@
      openSelect();
    }
  }
function openSelect() {
     //打开选人浮层
    dispatch({ type: 'share/update', payload: { visible: true } });
    let selection = window.getSelection();//创建selection,用法可以直接去MDN 去查看 https://developer.mozilla.org/zh-CN/docs/Web/API/Selection
    setFocusNode(selection.focusNode); // 缓存光标所在节点
    setFocusOffset(selection.focusOffset); // 缓存光标所在节点位置
    let inputId = document.getElementById('talkInput');
    inputId.blur();
  }

选完人后

 function onSelectSubmit(val) {
 //val 选择的人的信息
    let userList = val.sharedUserIds;
    setDirectUsers([...directUsers, ...userList]);
    let inputId = document.getElementById('talkInput');
    let selection = window.getSelection();
    let range = window.getSelection().getRangeAt(0);
    //选中输入的@符号
    range.setStart(focusNode, focusOffset - 1);
    range.setEnd(focusNode, focusOffset);
    //删除输入的@符号
    range.deleteContents();

    userList.forEach((s) => {
      var spanNode1 = document.createElement('span');
      var spanNode2 = document.createElement('span');
      spanNode1.className = styles.atFont;
      spanNode1.innerHTML = '@' + s.nickName;
      spanNode1.contentEditable = false;//设置为不可编辑,是为了整个@xxx一起删掉
      spanNode1.setAttribute('data-userId', s.userId);
      spanNode2.innerHTML = '&nbsp;';
      var frag = document.createDocumentFragment(),
        node,
        lastNode;
      frag.appendChild(spanNode1);//在 Range 的起点处插入一个节点。
      while ((node = spanNode2.firstChild)) {
        lastNode = frag.appendChild(node);
      }
      range.insertNode(frag);
      selection.extend(lastNode, 1);
      selection.collapseToEnd();//将当前的选区折叠到最末尾的一个点。
    });
  }

删除@xxx的时候,要把@xxx一起删除

  function onTalkKeyDown(e) {
    let inputId = document.getElementById('talkInput');
    let delUserId = null,
      canDel = true;
    if (e.key == 'Backspace') {//按下删除键时
      let range = window.getSelection().getRangeAt(0);
      let removeNode = null;
      //焦点在@xxx后面的时候
      if (range.startOffset <= 1 && range.startContainer.parentElement.className.indexOf('atFont') < 0) {
        removeNode = range.startContainer.previousElementSibling;
      }
    //  点一下@xxx的时候,直接删除@xxx的时候,此时range.startContainer是@xxx,range.startContainer.parentElement是<span>@xxx</span>
      if (range.startContainer.parentElement.className.indexOf('atFont') >= 0) {
        removeNode = range.startContainer.parentElement;
      }
      if (removeNode) {
        delUserId = removeNode.getAttribute('data-userid');
        inputId.removeChild(removeNode);
      }

      let userList = [...directUsers],
        newUserList = [];
      userList.forEach((s) => {
        if (canDel && s.userId === delUserId) {
          canDel = false;
        } else {
          newUserList = [...newUserList, { ...s }];
        }
      });
      setDirectUsers([...newUserList]);
    }
  }

结束。