Vue如何实现一个长尾词自动提示的文本输入框功能?

2026-04-02 07:011阅读0评论SEO教程
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2666个文字,预计阅读时间需要11分钟。

Vue如何实现一个长尾词自动提示的文本输入框功能?

目录 + 知识预设 + 需求分析 + 实现 + 创造能力 + 输入文本的文本框框架 + 添加at功能 + 日志 + 知识预设 + 基于Vue手把手教你实现一个具有@人功能的文本编辑器(微信群的输入框) + 选择 + 对象

目录
  • 知识前置
    • 需求分析
  • 实现
    • 创建能够输入文本的文本框
    • 添加at功能
  • 后记

    知识前置

    基于vue手把手教你实现一个拥有@人功能的文本编辑器(其实就是微信群聊的输入框)

    Selection对象,表示用户选择的文本范围或插入符号的当前

    developer.mozilla.org/zh-CN/docs/…

    contenteditable是一个枚举属性,表示元素是否可被用户编辑。

    developer.mozilla.org/zh-CN/docs/…

    需求分析

    • 文本框能够输入文本(太简单了)
    • 能够at人

    实现

    创建能够输入文本的文本框

    在这里主要利用 contenteditable属性,让创建的 div 能够编辑

    利用input事件监听数据变化,将数据同步出去

    <!--main.vue!--> <template> <div> <Editor v-model="value"/> </div> </template>

    <!--editor.vue!--> <template> <div> <div class="editor" contenteditable="true" @input="input" /> </div> </template> <script> export default { computed: { editor() { return this.$refs.editor || {} } }, methods: { input(e) { this.$emit('input', this.getEditorHtml()) }, getEditorHtml() { return this.editor.innerHTML || '' } } } </script> <style lang="less" scoped> .editor{ overflow-y: auto; background: #F4F6FB; border-radius: 4px; border: 1px solid transparent; min-height: 40px; max-height:200px; padding: 14px 9px; line-height: 20px; &:empty{ &::before{ content:'输入你想对他/她说的话,然后@她!'; color: #999; } } &:focus{ outline: none; border-color: #3656C6; border-radius: 4px; } } </style>

    效果如下图所示

    这个时候我们就实现了一个能够绑定数据的文本输入框,第一个需求完美实现,接下来实现第二个需求(开始折磨)

    添加at功能

    这里的需求主要分四步走

    • 当用户输入@字符时,弹出用户选择列表
    • 当用户点击@的人时,收回@列表
    • 将@的人嵌入到文本框中
    • 删除@的人时,要直接整块删除

    首先我们先实现一个用户选择的列表,这里主要涉及到的都是界面的编辑和动画的设置,不展开描述,直接上效果图**(完整代码会在文末给出)**

    接着我们要改造input函数,检测当用户输入为@符号时,弹出选择框

    input(e) { if (e.data === '@') { // 弹出用户选择框 this.$refs.UserList.show() // 失去焦点,退出手机的软体键盘 this.editor.blur() } this.$emit('input', this.getEditorHtml()) },

    当用户点击要@的人时,关闭选择列表,同时将@人的人插入到文本框中

    userItemClick(item) { const dom = this.createAtDom(item) this.$refs.editor.innerHTML = this.$refs.editor.innerHTML + dom.outerHTML this.$refs.UserList.close() }, createAtDom(item) { const dom = document.createElement('span') dom.classList.add('active-text') // 这里的contenteditable属性设置为false,删除时可以整块删除 dom.setAttribute('contenteditable', 'false') // 将id存储在dom元素的标签上,便于后续数据处理 dom.setAttribute('data-id', item.id) dom.innerHTML = `&nbsp@${item.name}&nbsp` return dom },

    效果入下图所示

    相信有不少朋友已经发现问题了,这种方式只能怪将@的人添加到文本的最末尾,但如果我编辑文本的时候,光标的位置不是在文本的最后,而是在文本之间的某个位置,那此时我们这么添加@的人就会有点反直觉。

    所以我们在弹出选择列表的时候,要把当前光标所处的位置标记下来,插入时,就插入到对应的位置上。所以此时就要抛出我们本文最重要的一个对象

    Selection对象

    我们要利用 Selection对象的 anchorOffset属性去获取当前焦点的位置,此时我们改造input函数,添加 saveIndex 方法,在弹出文本框失焦之前,保存当前焦点的位置。

    //改造input函数 input(e) { if (e.data === '@') { // 保存焦点位置 this.saveIndex() // 弹出用户选择框 this.$refs.UserList.show() // 失去焦点,退出手机的软体键盘 this.editor.blur() } this.$emit('input', this.getEditorHtml()) }, // 添加saveIndex方法 async saveIndex() { // 获取selection对象 const selection = getSelection() // 保存当前焦点的位置 this.selectionIndex = selection.anchorOffset },

    // 改造userItemClick函数 userItemClick(item) { const dom = this.createAtDom(item) this.addData(item) this.$refs.UserList.close() }, // 添加dom节点到指定位置 addData(item){ const html = this.editor.innerHTML const leftInnerHtml = html.substring(0, this.selectionIndex - 1) const dom = this.createAtDom(item) const rightInnerHtml = html.substring(this.selectionIndex, html.length) this.editor.innerHTML = leftInnerHtml + dom.outerHTML + rightInnerHtml }

    这个时候我们就可以把@的人添加到我们之前光标的位置了,效果如下如所示

    但在某天,你突发奇想,想同时对很多个女神发出邀请,这个时候你发现,@多人的时候,出现问题了

    我们插入的@人的节点被硬生生拆成了字符串,这很明显跟我们的预期有差别呀,这个时候我们应该分析一下我们编辑时的dom结构,如下图所示

    Vue如何实现一个长尾词自动提示的文本输入框功能?

    为了便于理解我画了个简单的图

    我们在插入dom节点之前,文本框的所有内容都是属于editor节点下唯一一个textNode节点,插入dom节点之后,editor节点新增了一个子节点,而 Selection.anchorOffset 这个属性获取到的焦点位置,实际上是相对于当前所处node节点而言的(←理解这个概念,非常重要)

    也就是说

    我们第一次插入dom节点,焦点位置是相对于当前节点,也就是editor节点下的唯一一个textNode节点计算

    第二次插入dom节点,焦点位置是相对于当前节点,也就是当前textNode节点计算

    后续插入的dom节点,焦点位置计算方式同上

    所以当我们有如下需求的时候

    Selection.anchorOffset 的返回值是5,而我们的addData方法,实际上是从editor.innerHtml的第一个位置开始算,第五个位置刚好插到了span节点的里面,所以就出现了上文乱码的问题。

    所以我们解决的方案,就是在保存焦点位置的时候,同时保存当前编辑的那个textNode节点,那我们怎么找到当前正在编辑的那个textNode节点呢?

    Selection 对象提供了一个方法 Selection.containsNode()

    mdn文档是这么描述的:判断指定的节点是否包含在 Selection 中 (是否被选中)

    在我们这个场景中,通俗点讲就是,我这个节点到底是不是编辑的节点?是你就返回true,不是就false

    所以我们可以在弹出用户选择框之前,遍历一下editor节点的子节点,找出我们当前编辑的那个textNode节点

    // 改造一下saveIndex async saveIndex() { const selection = getSelection() this.selectionIndex = selection.anchorOffset const nodeList = this.editor.childNodes // 保存当前编辑的dom节点 for (const [index, value] of nodeList.entries()) { // 这里第二个参数要配置成true,没配置有其他的一些小bug,这里不展开讲,详细可以看文档 if (selection.containsNode(value, true)) { this.dom = value this.domIndex = index } } },

    现在当前编辑的节点和编辑的位置都已经保存下来了,剩下的就是把@人的节点插入到我们编辑的那个textNode节点里面就完成了。

    // 改造一下addData方法 addData(item) { const html = this.dom.textContent const leftText = html.substring(0, this.selectionIndex - 1) const dom = this.createAtDom(item) const rightText = html.substring(this.selectionIndex, html.length) this.dom.textContent = leftText + dom.outerHTML + rightText },

    然而,当我们再次运行代码调试的时候,出现了我们预期外的结果

    是我们代码有问题吗?说是其实不算是,说不是,其实也算是(废话)

    其实是因为我们编辑的是textNode节点,而textNode节点就算包含了dom结构,他也是把结构当成文本输出到页面上,所以在这里

    • 我们应该创建一个新的结构,也就是我们的文档片段DocumentFragment
    • 然后把我们的节点结构插入到DocumentFragment
    • 接着利用Node.insertBefore()方法,把DocumentFragment插入到原来编辑的textNode节点之前,再用Node.removeChild()方法把原来编辑的textNode节点删除
    • 这样就可以实现正常的插入

    为了方便理解,可以看一下流程图

    addData(item) { const text = document.createDocumentFragment() const span = document.createElement('span') const html = this.dom.textContent // 左边的节点 const textLeft = document.createTextNode(html.substring(0, this.selectionIndex - 1) + '') // 这里如果textLeft是个空的文本节点,会导致@用户无法删除,这里添加一个判断,如果是空,则插入一个空的span节点 text.appendChild(textLeft.textContent ? textLeft : span) // 加入@人的节点 text.appendChild(this.createAtDom(item)) // 右边的节点 const textRight = document.createTextNode(html.substring(this.selectionIndex, html.length)) textRight.textContent && text.appendChild(textRight) this.editor.insertBefore(text, this.dom) this.editor.removeChild(this.dom) },

    当我们处理到这里时,就可以多次at想要at的人,效果如图

    后续我们要将数据提取出来,可以根据v-model绑定的value进行解析,把插在标签里的数据提取出来,也可以根据自己的业务插入一些数据,这里不是重点,也不展开讲

    后记

    基本上文本编辑器的核心逻辑到这里就讲完了,但是这个demo在做的过程中,有好几个地方做了优化,特别是针对移动端软体键盘的进入和离开,还有焦点的对焦和失焦,都做了一些处理,但是在文章里头没有展开讲

    想要详细了解的大佬们可以到我github仓库下载源码 github.com/adouni1996/…

    以上就是vue实现At人文本输入框示例详解的详细内容,更多关于vue At人文本输入框的资料请关注易盾网络其它相关文章!

    本文共计2666个文字,预计阅读时间需要11分钟。

    Vue如何实现一个长尾词自动提示的文本输入框功能?

    目录 + 知识预设 + 需求分析 + 实现 + 创造能力 + 输入文本的文本框框架 + 添加at功能 + 日志 + 知识预设 + 基于Vue手把手教你实现一个具有@人功能的文本编辑器(微信群的输入框) + 选择 + 对象

    目录
    • 知识前置
      • 需求分析
    • 实现
      • 创建能够输入文本的文本框
      • 添加at功能
    • 后记

      知识前置

      基于vue手把手教你实现一个拥有@人功能的文本编辑器(其实就是微信群聊的输入框)

      Selection对象,表示用户选择的文本范围或插入符号的当前

      developer.mozilla.org/zh-CN/docs/…

      contenteditable是一个枚举属性,表示元素是否可被用户编辑。

      developer.mozilla.org/zh-CN/docs/…

      需求分析

      • 文本框能够输入文本(太简单了)
      • 能够at人

      实现

      创建能够输入文本的文本框

      在这里主要利用 contenteditable属性,让创建的 div 能够编辑

      利用input事件监听数据变化,将数据同步出去

      <!--main.vue!--> <template> <div> <Editor v-model="value"/> </div> </template>

      <!--editor.vue!--> <template> <div> <div class="editor" contenteditable="true" @input="input" /> </div> </template> <script> export default { computed: { editor() { return this.$refs.editor || {} } }, methods: { input(e) { this.$emit('input', this.getEditorHtml()) }, getEditorHtml() { return this.editor.innerHTML || '' } } } </script> <style lang="less" scoped> .editor{ overflow-y: auto; background: #F4F6FB; border-radius: 4px; border: 1px solid transparent; min-height: 40px; max-height:200px; padding: 14px 9px; line-height: 20px; &:empty{ &::before{ content:'输入你想对他/她说的话,然后@她!'; color: #999; } } &:focus{ outline: none; border-color: #3656C6; border-radius: 4px; } } </style>

      效果如下图所示

      这个时候我们就实现了一个能够绑定数据的文本输入框,第一个需求完美实现,接下来实现第二个需求(开始折磨)

      添加at功能

      这里的需求主要分四步走

      • 当用户输入@字符时,弹出用户选择列表
      • 当用户点击@的人时,收回@列表
      • 将@的人嵌入到文本框中
      • 删除@的人时,要直接整块删除

      首先我们先实现一个用户选择的列表,这里主要涉及到的都是界面的编辑和动画的设置,不展开描述,直接上效果图**(完整代码会在文末给出)**

      接着我们要改造input函数,检测当用户输入为@符号时,弹出选择框

      input(e) { if (e.data === '@') { // 弹出用户选择框 this.$refs.UserList.show() // 失去焦点,退出手机的软体键盘 this.editor.blur() } this.$emit('input', this.getEditorHtml()) },

      当用户点击要@的人时,关闭选择列表,同时将@人的人插入到文本框中

      userItemClick(item) { const dom = this.createAtDom(item) this.$refs.editor.innerHTML = this.$refs.editor.innerHTML + dom.outerHTML this.$refs.UserList.close() }, createAtDom(item) { const dom = document.createElement('span') dom.classList.add('active-text') // 这里的contenteditable属性设置为false,删除时可以整块删除 dom.setAttribute('contenteditable', 'false') // 将id存储在dom元素的标签上,便于后续数据处理 dom.setAttribute('data-id', item.id) dom.innerHTML = `&nbsp@${item.name}&nbsp` return dom },

      效果入下图所示

      相信有不少朋友已经发现问题了,这种方式只能怪将@的人添加到文本的最末尾,但如果我编辑文本的时候,光标的位置不是在文本的最后,而是在文本之间的某个位置,那此时我们这么添加@的人就会有点反直觉。

      所以我们在弹出选择列表的时候,要把当前光标所处的位置标记下来,插入时,就插入到对应的位置上。所以此时就要抛出我们本文最重要的一个对象

      Selection对象

      我们要利用 Selection对象的 anchorOffset属性去获取当前焦点的位置,此时我们改造input函数,添加 saveIndex 方法,在弹出文本框失焦之前,保存当前焦点的位置。

      //改造input函数 input(e) { if (e.data === '@') { // 保存焦点位置 this.saveIndex() // 弹出用户选择框 this.$refs.UserList.show() // 失去焦点,退出手机的软体键盘 this.editor.blur() } this.$emit('input', this.getEditorHtml()) }, // 添加saveIndex方法 async saveIndex() { // 获取selection对象 const selection = getSelection() // 保存当前焦点的位置 this.selectionIndex = selection.anchorOffset },

      // 改造userItemClick函数 userItemClick(item) { const dom = this.createAtDom(item) this.addData(item) this.$refs.UserList.close() }, // 添加dom节点到指定位置 addData(item){ const html = this.editor.innerHTML const leftInnerHtml = html.substring(0, this.selectionIndex - 1) const dom = this.createAtDom(item) const rightInnerHtml = html.substring(this.selectionIndex, html.length) this.editor.innerHTML = leftInnerHtml + dom.outerHTML + rightInnerHtml }

      这个时候我们就可以把@的人添加到我们之前光标的位置了,效果如下如所示

      但在某天,你突发奇想,想同时对很多个女神发出邀请,这个时候你发现,@多人的时候,出现问题了

      我们插入的@人的节点被硬生生拆成了字符串,这很明显跟我们的预期有差别呀,这个时候我们应该分析一下我们编辑时的dom结构,如下图所示

      Vue如何实现一个长尾词自动提示的文本输入框功能?

      为了便于理解我画了个简单的图

      我们在插入dom节点之前,文本框的所有内容都是属于editor节点下唯一一个textNode节点,插入dom节点之后,editor节点新增了一个子节点,而 Selection.anchorOffset 这个属性获取到的焦点位置,实际上是相对于当前所处node节点而言的(←理解这个概念,非常重要)

      也就是说

      我们第一次插入dom节点,焦点位置是相对于当前节点,也就是editor节点下的唯一一个textNode节点计算

      第二次插入dom节点,焦点位置是相对于当前节点,也就是当前textNode节点计算

      后续插入的dom节点,焦点位置计算方式同上

      所以当我们有如下需求的时候

      Selection.anchorOffset 的返回值是5,而我们的addData方法,实际上是从editor.innerHtml的第一个位置开始算,第五个位置刚好插到了span节点的里面,所以就出现了上文乱码的问题。

      所以我们解决的方案,就是在保存焦点位置的时候,同时保存当前编辑的那个textNode节点,那我们怎么找到当前正在编辑的那个textNode节点呢?

      Selection 对象提供了一个方法 Selection.containsNode()

      mdn文档是这么描述的:判断指定的节点是否包含在 Selection 中 (是否被选中)

      在我们这个场景中,通俗点讲就是,我这个节点到底是不是编辑的节点?是你就返回true,不是就false

      所以我们可以在弹出用户选择框之前,遍历一下editor节点的子节点,找出我们当前编辑的那个textNode节点

      // 改造一下saveIndex async saveIndex() { const selection = getSelection() this.selectionIndex = selection.anchorOffset const nodeList = this.editor.childNodes // 保存当前编辑的dom节点 for (const [index, value] of nodeList.entries()) { // 这里第二个参数要配置成true,没配置有其他的一些小bug,这里不展开讲,详细可以看文档 if (selection.containsNode(value, true)) { this.dom = value this.domIndex = index } } },

      现在当前编辑的节点和编辑的位置都已经保存下来了,剩下的就是把@人的节点插入到我们编辑的那个textNode节点里面就完成了。

      // 改造一下addData方法 addData(item) { const html = this.dom.textContent const leftText = html.substring(0, this.selectionIndex - 1) const dom = this.createAtDom(item) const rightText = html.substring(this.selectionIndex, html.length) this.dom.textContent = leftText + dom.outerHTML + rightText },

      然而,当我们再次运行代码调试的时候,出现了我们预期外的结果

      是我们代码有问题吗?说是其实不算是,说不是,其实也算是(废话)

      其实是因为我们编辑的是textNode节点,而textNode节点就算包含了dom结构,他也是把结构当成文本输出到页面上,所以在这里

      • 我们应该创建一个新的结构,也就是我们的文档片段DocumentFragment
      • 然后把我们的节点结构插入到DocumentFragment
      • 接着利用Node.insertBefore()方法,把DocumentFragment插入到原来编辑的textNode节点之前,再用Node.removeChild()方法把原来编辑的textNode节点删除
      • 这样就可以实现正常的插入

      为了方便理解,可以看一下流程图

      addData(item) { const text = document.createDocumentFragment() const span = document.createElement('span') const html = this.dom.textContent // 左边的节点 const textLeft = document.createTextNode(html.substring(0, this.selectionIndex - 1) + '') // 这里如果textLeft是个空的文本节点,会导致@用户无法删除,这里添加一个判断,如果是空,则插入一个空的span节点 text.appendChild(textLeft.textContent ? textLeft : span) // 加入@人的节点 text.appendChild(this.createAtDom(item)) // 右边的节点 const textRight = document.createTextNode(html.substring(this.selectionIndex, html.length)) textRight.textContent && text.appendChild(textRight) this.editor.insertBefore(text, this.dom) this.editor.removeChild(this.dom) },

      当我们处理到这里时,就可以多次at想要at的人,效果如图

      后续我们要将数据提取出来,可以根据v-model绑定的value进行解析,把插在标签里的数据提取出来,也可以根据自己的业务插入一些数据,这里不是重点,也不展开讲

      后记

      基本上文本编辑器的核心逻辑到这里就讲完了,但是这个demo在做的过程中,有好几个地方做了优化,特别是针对移动端软体键盘的进入和离开,还有焦点的对焦和失焦,都做了一些处理,但是在文章里头没有展开讲

      想要详细了解的大佬们可以到我github仓库下载源码 github.com/adouni1996/…

      以上就是vue实现At人文本输入框示例详解的详细内容,更多关于vue At人文本输入框的资料请关注易盾网络其它相关文章!