首页>>前端>>JavaScript->聊天框(番外篇)—如何实现@功能的整体删除

聊天框(番外篇)—如何实现@功能的整体删除

时间:2023-12-02 本站 点击:0

上一篇文章中,我们已经初步实现了聊天输入框,但其@功能是不完善的,例如无法整体删除、无法获取除用户名以外的数据(假设用户名不是唯一的)。有问题就要想办法解决,在网上百度了一圈后,倒是有一些收获。本文就着重解决@的整体删除以及获取额外数据。

准备工作

聊天框的实现是基于div+contenteditable的。一旦元素开启了可编辑属性,就可以像输入框一样输入内容。不同的是,我们可以传入HTML格式的代码,这也是可以渲染出来的。

<div contenteditable><p style="color: red;">hello</p></div><div contenteditable>hello</div>

回到我们之前实现的@功能,是直接插入的特殊字符串,删除时也只能一个一个字符的删除,当然也可以通过监听backspacedelete按键,结合正则表达式,手动移动光标来删除,这种方式过于复杂,笔者也没有搞明白要怎么操作;根据上述的渲染结果,我们是不是可以考虑将@xxx也当作一个HTML标签插入到输入框中,然后保证删除时能整体删除就可以了。

初步解决方案

在插入HTML标签前,我们先来了解替换元素非替换元素

替换元素是指浏览器会元素的标签和属性,来决定元素的具体显示内容,如果未指定属性则显示的将是一个空标签,传入不同的属性值其在页面上渲染出来的结果也不一样。常见的替换元素包括:imginputtextareaselectobject

非替换元素是指其内容可以直接展现给浏览器,HTML中大多数元素是不可替换元素。

了解了这个有什么用呢,我们可以试试在可编辑元素中插入一个替换元素标签:

<div contenteditable>    <input value="@xxx" readonly /></div><div contenteditable>    <img src="xxx" alt="@xxx" /></div>

可以发现,在输入框内是可以整体删除@xxx,基本功能是可以实现的,但是有几个问题需要解决:

input有默认的宽度,并且需要指定为只读,由于我们的@xxx宽度是不定的,需要使得input的宽度自适应,实现起来比较负责,笔者未实现

img标签需要指定有效的src属性,如果指定的src无效,即使有alt属性值,也会有一个裂开的图片。为了解决这个裂开的图片,笔者尝试了多种方法都没有解决,真是要裂开了。解决不了裂开的图片,笔者就尝试将@xxx通过html2canvascanvas2img的方式将其转换为base64的图片,效果也还行,就是转换的过程有点慢,也被pass掉了。

难道除了这种方式就没有别的了嘛,答案是有的。

最终解决方案

不是说使用inputimg标签不行,只是用起来有点麻烦,于是乎笔者就将目光转向了非替换元素。先来一个a标签试试水:

<div contenteditable>    <a contenteditable="false">@xxx</a></div>

可编辑元素的子元素默认也是可编辑的,因此需要设置a标签为不可编辑,不然就无法实现整体删除。运行上面的例子,可以发现基本上符合我们的预期效果,想要完美实现,还得考虑几点:

除了@xxx本身外,还应该携带唯一的标识,这样才能区分艾特了哪些人

如果是输入@,然后选择了具体的成员,那么之前输入的@应该被删除掉

如何保证光标是在@xxx后面

有了具体的问题,我们就来逐一解决:

额外参数

a标签可以在value中携带唯一标识,然后我们在发送文本时,从文本内容中取出被艾特的人即可。

<div contenteditable>    <a name="at" :value="username" contenteditable="false">@xxx</a></div>

删除之前输入的@

因为我们在a标签中已经包含了@,因此之前输入的@就需要删除。也许有人会想,我要通过调用backspce或者delete来实现,可惜这种方式并不可行。正确的思路应该是通过字符串的替换,来模拟实现删除功能。我们这里采用正则表达式的方式来处理,因为我们只需要将@xxx左边最近的一个@删除即可:

if (/@<a name="at"/.test(this.$refs.editor.innerHTML)) {    this.$refs.editor.innerHTML = this.$refs.editor.innerHTML.replace(/@<a name="at"/, '<a name="at"');}

我们使用name="at"只是替换在输入框中的@,如果有其他的a标签我们将不处理。使用直接替换的方式,会导致光标默认跑到开头,这显然不符合要求,接下来处理光标

处理光标位置

我们需要将光标插入到@xxx后面,具体是哪一个@xxx就需要通过getElementById()来查找,然后将光标移动到此元素后面,因此我们在插入a标签时,还应该指定每一个a标签的id,使用一个递增的全局变量即可。

// DivEditable.vueif (/@<a name="at"/.test(this.$refs.editor.innerHTML)) {    // 使用正则替换,将已经输入的@替换掉    // 如果直接赋值修改innerHTML,则光标默认会回到开头。因此需要额外处理光标    this.$refs.editor.innerHTML = this.$refs.editor.innerHTML.replace(/@<a name="at"/, '<a name="at"');    // id表示哪一个@    let el = document.getElementById(id);    range = document.createRange();    sel = window.getSelection();    // 将光标重新定位到自定义的a标签后面    range.setStartAfter(el);    range.setEndAfter(el);    sel.removeAllRanges();    sel.addRange(range);}

// InputBox.vueonSelect(item) {    this.atIndex++;    // 使用a标签表示@的成员    let at = `<a name="at" value="${item.userName}" tabindex="-1" id="${this.atIndex}" contenteditable="false" href="javascript:void(0)">@${item.name}</a>&#x200B;`;    this.$refs.inputBox.insertContent(at, this.atIndex);    console.log('onSelect', item);    // this.$refs.inputBox.insertContent(`${item.name} `); // 有空格    this.isShowAt = false;},

获取@的成员

我们通过正则表达式来获取

let atIds = [];this.$refs.editor.innerHTML.replace(/<a [^>]*value=['"]([^'"]+)[^>]*/gi, function(match, capture) {    atIds.push(capture);})

这样我们就基本实现了使用a标签来完成@的整体删除,这里有一个小细节。一般我们都会在@xxx后面有一个空格,我们可以使用&nbsp;,也可以使用零宽字符&#x200B;。笔者发现在不同的浏览器上,使用空格和零宽字符的效果还是有所差异的。

总结

本文介绍了使用a标签来完成@功能的整体删除,当然除了使用a标签,span、button、img等标签都是可以选择的技术方案,实现的原理都差不多。不管是采用哪种方案,都需要注意几点:

可编辑元素的子元素默认也是可编辑的,因此插入标签时需要设置为不可编辑

插入标签时,需要将已经输入的@字符删除

注意光标位置的处理

标签自身的样式需要部分覆盖,具体的看使用情况

若有其他解决方案,欢迎在评论区补充。最后,完整代码可参考 项目地址

原文:https://juejin.cn/post/7101539945809969188


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:/JavaScript/7794.html