一个实现@人效果的vue3组件

1,796 阅读3分钟

最近刚好有个需求需要实现在输入框里@人的效果,看到之前有人开发了 at.js,不过这个依赖jquery,对于目前的前端来说,很少会用到jquery了。因为我们项目用的是vue,也刚好找到了个vue-at,但这个组件其实实现的有点小问题,在用户用快捷键撤销的时候会无法撤销插入的标签。研究了一下,发现是因为他底层实现,是直接使用了dom元素的插入,而浏览器不认为这是用户操作的,所以是无法撤销的,如果我们需要可以撤销的效果,是需要使用document.execCommand这个api来实现相关操作。

目前组件已发布,欢迎学习和使用:github.com/SHISME/vue3…

下面分享一下开发这个组件的关键点:

关键功能点

image.png

首先确认这个组件的一些关键的交互:

  • 当输入@时候展示对应的列表,并根据@后面的关键字进行匹配
  • 选中列表中的人后,将选中的人插入到输入框里并高亮
  • 删除的时候高亮模块能够整块删除

关键功能点实现思路

首先普通的输入框肯定是无法满足我们高亮输入项这个需求点的,而html也刚好提供了 contenteditable 这个属性,我们可以看到市面上很多复杂的富文本编辑器都是用这个属性来实现的。

如何响应@的输入

监听输入的方式无外乎两种,监听keydown或者input,这里其实用这两个事件都行,但考虑到input是实打实的输入,所以选用了监听input的方式来实现。

  1. 当我们监听到input事件时,可以通过 document.getSelection 这个api来拿到当前光标的位置,然后我们可以拿到光标前面的内容。
const selection = window.getSelection();
if (selection && selection.rangeCount > 0) {
  const range = selection.getRangeAt(0); // range对象,能够获取到光标的一些相关信息
}
  1. 拿到内容后,我们只需要截取出@关键字后面的内容,这部分内容就是我们需要匹配的内容了。
  2. 在匹配到内容后,将对应的列表展示给用户选择

如何将选中项高亮插入到模块之中

可以通过 document.execCommand('insertHTML', false, '<span>something</span>') 来实现插入html的能力,使用这个api插入的内容是支持撤销的。不过插入之前我们需要将 @ 和@后面输入的内容给移除掉,但其实这一步只需要我们,通过range对象将这部分内容选中后,在执行 document.execCommand('insertHTML', false, '') 浏览器就会把这部分内容给替换掉了,我们也不用单独做删除的操作了。

删除整个高亮模块

这个之前本来想着是将插入的高亮模块的 contenteditable 设置为false,这样既满足了,整个模块整体删除,也满足了对插入模块无法二次编辑这个需求。但是这样处理也会造成一个问题,就是用户用快捷键做撤销操作的时候,也是无法撤销插入这个操作的,具体原因暂时没找到为啥。

最后就用了监听keydown的方式来实现这个功能,实现的方式就是,在监听到按下删除键的时候,判断一下光标的位置,如果光标在高亮模块里面或者高亮模块的后面的时候就删除整个高亮模块。但实际开发的时候会发现,只要光标紧贴着高亮模块,无论是在光标的最前面或者最后面,range.endContainer都会是高亮模块的dom,所以就需要借助range.endOffset来判断光标是在高亮模块的哪里,如果实在高亮模块的里面或者后面,则用range选中整个高亮模块的dom,然后执行document.execCommand('delete') 这样删除的操作也是可以通过快捷键撤销的,而如果使用dom的remove,则是无法通过快捷键撤销的。

// 把整个tag删除
range.selectNode(tagContainer);
selection.removeAllRanges();
selection.addRange(range);
document.execCommand("delete");