全部
常见问题
产品动态
精选推荐

Vue实战:轻松掌握输入框@功能实现技巧

管理 管理 编辑 删除

成员列表

创建

实现成员列表的方式比较简单,其实就是一个列表,一个简单的v-for循环就可以搞定,点击时将当前选择的成员项回调给父组件。

新增一个AtPop.vue文件:

<template>
  <div class="at-pop-index">
    <div v-for="(item, index) in listData" :key="index" class="at-pop-item" @click.stop="clickItem(item)">{{item.name}}</div>
  </div>
</template>
<script>
export default {
  props: {
    // 传入需要被@的成员数组
    listData: {
      type: Array,
      default: () => []
    }
  },
  methods: {
    clickItem(item) {
      this.$emit('onSelect', item); // 调用父组件处理成员选择的方法,并回传当前选择项
    }
  }
}
</script>

使用

在父组件中,注册并使用成员列表组件。我们需要在用户输入@的时候弹出成员列表,因此需要监听用户的输入,然后在用户选择成员后需要关闭。关键是获取光标位置,这个由输入框获取,在父组件只需要使用即可。

// 核心代码
...
// 选择成员时插入数据,并关闭弹窗
onSelect(item) {
    console.log('onSelect', item);
    this.$refs.inputBox.insertContent(`${item.name} `); // 有空格
    this.isshowAt = false;
},
// 输入框输入时回调函数
inputFunc(data, event) {
    console.log('inputFunc', data, event);
    if (event.data === '@') {
        this.isShowAt = true; // 显示弹窗
        this.$nextTick(() => {
        let dom = document.getElementsByClassName('at-pop-index')[0]; // 获取成员列表弹窗,需要放在nextTick中
        // 设置位置
        dom.style.position = 'fixed';
        dom.style.left = Math.floor(data.left + 10) + 'px';
        dom.style.top = Math.floor(data.top) + 'px';
        dom.style.zIndex = 9999;
        })
    } else {
        this.isShowAt = false;
    }
},
...

输入框

输入框需要处理光标位置的获取、将值插入到光标的位置等,是本次功能实现的核心。

当输入框聚焦时,我们会看到光标闪动,想要获取光标的位置以便于插入数据,则需要借助Selection对象。Selection表示用户选择的一段文本范围或者插入数据的当前位置。既然是获取选取范围,那当前选择范围的index=0就是当前光标的位置。我们想要实现的效果是成员列表跟随光标移动,因此就需要获取光标的坐标值。

获取光标的坐标

let range = window.getSelection().getRangeAt(0); // 获取当前光标
let position = range.getBoundinGClientRect(); // 获取当前光标的位置

getBoundingClientRect()方法会返回一个DOMRect矩形对象,其包含矩形区域的坐标值。将获取到的坐标值回调给父组件的方法,显示成员列表。

当我们在输入框输入@的时候,页面会出现成员列表,此时输入框还是聚焦的。但是如果我们点击了成员列表的某一项,此时输入框已经失焦了,虽然我们可以获取选择的成员并插入,如果只是简单的字符串追加的话,光标会在下次输入时默认定位到开头;或者我们需要在中间插入选择的成员,会发现没有位置可以插入。因此我们需要在失焦的时候先保存当前光标,并在插入时还原光标。

保存光标

// DivEditable.vue
// 失焦
inputBlur(event) {
    this.selection = this.saveSelection();
    this.$emit('blurFunc', event);
},
// 失焦时保存光标
saveSelection() {
    if (!window.getSelection) {
        return null;
    }
    let sel = window.getSelection();
    if (sel.getRangeAt && sel.rangeCount) {
        return sel.getRangeAt(0);
    }
},

光标暂存了,我们需要将插入成员封装成方法,并可以给父组件调用,这样父组件在获取到成员信息后就可以直接调用。接下来需要使用上面已经保存的光标位置:

插入文本

// DivEditable.vue
// 恢复光标
restoreSelection(range) {
    if (range) {
        let sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    }
},
// 插入数据
insertContent(value) {
    // this.$refs.editor.focus();
    let range, node;
    this.restoreSelection(this.selection); // 还原失焦前的光标位置
    range = window.getSelection().getRangeAt(0);
    range.collapse(false); // 光标移动到最后
​
    node = range.createContextualFragment(value);
    let child = node.lastChild;
    console.log('lastChild', child);
    range.insertNode(node);
    // 将光标的起始位置设置在当前插入的元素后面
    if (child) {
        range.setEndAfter(child);
        range.setStartAfter(child);
    }
​
    let sel = window.getSelection();
    sel.removeAllRanges();
    sel.addRange(range);
    this.$emit('input', this.$refs.editor.innerhtml);
},

其实到这里就基本实现了@功能,但是还有一个问题,当输入框失焦时回调了父组件的blurFunc方法,导致成员列表关闭了,但是数据还没有拿到。处理这种问题,有两种思路:

  • 使用setTimeout设置定时器,延后执行关闭成员列表操作
  • 修改取数逻辑为异步操作,等到数据拿到后才关闭成员列表

简单点,就采用setTimeout实现。

// InputBox.vue
blurFunc(event) {
    // 失焦时延时关闭弹窗,避免还未拿到数据
    if (this.isShowAt) {
        setTimeout(() => {
            this.isShowAt = false;
        }, 500);
    }
},

运行结果

输入框输入@,在光标位置附近弹出成员列表

63561202404171648434172.png

选择成员后,将成员信息插入到输入框中

780b3202404171649043521.png

总结

本文介绍了实现输入框@功能的方法,简单易上手

主要使用了SelectionRange对象的相关方法,完成对光标的处理以及输入的插入

本文实现的@功能,无法删除时整体删除。目前有两种思路:将@xxx转换为图片并插入到输入框,笔者简单写了demo验证了一下,效率不高,且会有卡顿;另一种方式就是监听退格键,删除时判断删除对象是否被包含着有意义的@xxx中,如果是则整体删除,如果不是则默认的方式删除,这种方式笔者没有尝试,难度比较大。

来源|编程网 作者:泡泡鱼

请登录后查看

CRMEB-慕白寒窗雪 最后编辑于2024-04-17 16:51:15

快捷回复
回复({{post_count}}) {{!is_user ? '我的回复' :'全部回复'}}
回复从新到旧

{{item.user_info.nickname ? item.user_info.nickname : item.user_name}}

作者 管理员 企业

{{item.floor}}# 同步到gitee 已同步到gitee {{item.is_suggest==1? '取消推荐': '推荐'}}
{{item.floor}}#
{{item.user_info.title}}
附件

{{itemf.name}}

{{item.created_at}}  {{item.ip_address}}
{{item.like_count}}
{{item.showReply ? '取消回复' : '回复'}}
删除
回复
回复

{{itemc.user_info.nickname}}

{{itemc.user_name}}

作者 管理员 企业

回复 {{itemc.comment_user_info.nickname}}

附件

{{itemf.name}}

{{itemc.created_at}}   {{itemc.ip_address}}
{{itemc.like_count}}
{{itemc.showReply ? '取消回复' : '回复'}}
删除
回复
回复
查看更多
回复
回复
175
{{like_count}}
{{collect_count}}
添加回复 ({{post_count}})

相关推荐

CRMEB-慕白寒窗雪 作者
社区运营专员---高冷のBoy | 呆萌のGirl

回答

2261

发布

1777

经验

45360

快速安全登录

使用微信扫码登录
{{item.label}} {{item.label}} {{item.label}} 板块推荐 常见问题 产品动态 精选推荐 首页头条 首页动态 首页推荐
加精
取 消 确 定
回复
回复
问题:
问题自动获取的帖子内容,不准确时需要手动修改. [获取答案]
答案:
提交
bug 需求 取 消 确 定

微信登录/注册

切换手机号登录

{{ bind_phone ? '绑定手机' : '手机登录'}}

{{codeText}}
切换微信登录/注册
暂不绑定
CRMEB客服

CRMEB咨询热线 咨询热线

400-8888-794

微信扫码咨询

CRMEB开源商城下载 开源下载 CRMEB官方论坛 帮助文档
返回顶部 返回顶部
CRMEB客服