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

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

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

准备工作

聊天框的实现是基于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后面

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

  1. 额外参数

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

    <div contenteditable>
        <a name="at" :value="username" contenteditable="false">@xxx</a>
    </div>
  2. 删除之前输入的@

    因为我们在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标签我们将不处理。使用直接替换的方式,会导致光标默认跑到开头,这显然不符合要求,接下来处理光标

  3. 处理光标位置

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

    // DivEditable.vue
    if (/@<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.vue
    onSelect(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;
    },
  4. 获取@的成员

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

    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等标签都是可以选择的技术方案,实现的原理都差不多。不管是采用哪种方案,都需要注意几点:

  • 可编辑元素的子元素默认也是可编辑的,因此插入标签时需要设置为不可编辑
  • 插入标签时,需要将已经输入的@字符删除
  • 注意光标位置的处理
  • 标签自身的样式需要部分覆盖,具体的看使用情况

最后,完整代码可参考 项目地址

相关文章
|
6月前
|
存储 C语言
【C深度解剖】计算机数据下载和删除原理
【C深度解剖】计算机数据下载和删除原理
|
7月前
|
UED
软件开发常见流程,好的用户体验,智能引导助手,介绍软件相关操作,会画个键盘,对键盘的相关键进行标注,效果动态展示图怎样画????弄一个图标,相关介绍
软件开发常见流程,好的用户体验,智能引导助手,介绍软件相关操作,会画个键盘,对键盘的相关键进行标注,效果动态展示图怎样画????弄一个图标,相关介绍
|
5月前
|
前端开发
【前端web入门第五天】03 清除默认样式与外边距问题【附综合案例产品卡片与新闻列表】
本文档详细介绍了CSS中清除默认样式的方法,包括清除内外边距、列表项目符号等;探讨了外边距的合并与塌陷问题及其解决策略;讲解了行内元素垂直边距的处理技巧;并介绍了圆角与盒子阴影效果的实现方法。最后通过产品卡片和新闻列表两个综合案例,展示了所学知识的实际应用。
108 11
|
8月前
|
人工智能 资源调度 前端开发
Hello~ ProChat 1.0 : 会话组件中 的“亿” 点点细节
Hello~ ProChat 1.0 : 会话组件中 的“亿” 点点细节
126 0
|
9月前
|
前端开发
前端知识笔记(十三)———单全选框控制方法,炒鸡无敌方便!!!
前端知识笔记(十三)———单全选框控制方法,炒鸡无敌方便!!!
45 0
|
存储 小程序 前端开发
【易售小程序项目】小程序私聊页面完善(带尾巴聊天气泡组件封装、滑至顶端获取历史聊天数据逻辑优化)【后端基于若依管理系统开发】
【易售小程序项目】小程序私聊页面完善(带尾巴聊天气泡组件封装、滑至顶端获取历史聊天数据逻辑优化)【后端基于若依管理系统开发】
86 0
|
前端开发
第34/90步《前端篇》第8章 重构记分板、背景、页面和游戏对象 第25课
今天学习《前端篇》第8章 重构记分板、背景、页面和游戏对象 第25课 重构游戏对象,这节课我们继续模块化重构,将页面类拆分到单独的文件中,清除一些不再需要的变量等,让游戏代码的结构更加合理。
78 0
|
前端开发
第33/90步《前端篇》第8章 重构记分板、背景、页面和游戏对象 第24课
今天学习《前端篇》第8章 重构记分板、背景、页面和游戏对象 第24课 创建页面对象,这节课开始创建游戏页面。
89 0
|
前端开发
第31/90步《前端篇》第8章 重构记分板、背景、页面和游戏对象 第22课
今天学习《前端篇》第8章 重构记分板、背景、页面和游戏对象 第22课 创建记分板模块,这节课我们将实现记分板对象的模块化。目前,游戏中还没有记分板对象,在屏幕下方只有两个分数,一个是用户的,一个是系统的,这节课将仿照挡板的设计方法创建一个记分板基类,然后再派生出两个子类,即用户记分板与系统记分板。
108 0
|
前端开发
第32/90步《前端篇》第8章 重构记分板、背景、页面和游戏对象 第23课
今天学习《前端篇》第8章 重构记分板、背景、页面和游戏对象 第23课 创建游戏背景对象和游戏对象,这节课我们尝试创建游戏背景对象和游戏对象。
69 0

热门文章

最新文章