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

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

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

准备工作

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

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

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

相关文章
|
1月前
|
UED
软件开发常见流程,好的用户体验,智能引导助手,介绍软件相关操作,会画个键盘,对键盘的相关键进行标注,效果动态展示图怎样画????弄一个图标,相关介绍
软件开发常见流程,好的用户体验,智能引导助手,介绍软件相关操作,会画个键盘,对键盘的相关键进行标注,效果动态展示图怎样画????弄一个图标,相关介绍
|
2月前
|
编解码 前端开发 JavaScript
带您一步步构建一个基本的动态新闻网站,包括页面布局、样式设计以及交互效果的实现
【6月更文挑战第14天】构建动态新闻网站实战项目,涉及页面布局、样式设计和交互实现。首页采用顶部导航栏、轮播图和新闻列表布局;新闻列表页按分类显示新闻,详情页展示完整内容并可添加相关推荐和评论。设计注重色彩搭配、字体选择和布局间距,实现轮播图效果、导航栏交互和响应式设计,提升用户体验。该项目有助于锻炼HTML和CSS技能,理解网页设计实际应用。
60 1
|
10月前
|
索引
【 uniapp - 黑马优购 | 分类界面 】创建cate分支、数据获取、动态渲染
【 uniapp - 黑马优购 | 分类界面 】创建cate分支、数据获取、动态渲染
125 0
|
10月前
|
存储 小程序 前端开发
【易售小程序项目】小程序私聊页面完善(带尾巴聊天气泡组件封装、滑至顶端获取历史聊天数据逻辑优化)【后端基于若依管理系统开发】
【易售小程序项目】小程序私聊页面完善(带尾巴聊天气泡组件封装、滑至顶端获取历史聊天数据逻辑优化)【后端基于若依管理系统开发】
44 0
|
10月前
|
存储 小程序 算法
【易售小程序项目】小程序首页完善(滑到底部数据翻页、回到顶端、基于回溯算法的两列数据高宽比平衡)【后端基于若依管理系统开发】
【易售小程序项目】小程序首页完善(滑到底部数据翻页、回到顶端、基于回溯算法的两列数据高宽比平衡)【后端基于若依管理系统开发】
72 0
|
12月前
|
前端开发 区块链
合成游戏看广告视频盒子系统开发方案逻辑/详细案例/功能设计/需求步骤/规则项目/源码说明
在Solidity中,与外部合约交互可以通过调用函数来完成。这些函数可以是在Solidity合约中定义的函数,也可以是在外部合约中定义的函数。调用外部合约函数需要知道合约的地址和函数的签名。
|
存储 前端开发 JavaScript
你可能需要的多文档页面交互方案(一)
你可能需要的多文档页面交互方案
109 1
|
前端开发
第33/90步《前端篇》第8章 重构记分板、背景、页面和游戏对象 第24课
今天学习《前端篇》第8章 重构记分板、背景、页面和游戏对象 第24课 创建页面对象,这节课开始创建游戏页面。
63 0
|
前端开发
第32/90步《前端篇》第8章 重构记分板、背景、页面和游戏对象 第23课
今天学习《前端篇》第8章 重构记分板、背景、页面和游戏对象 第23课 创建游戏背景对象和游戏对象,这节课我们尝试创建游戏背景对象和游戏对象。
53 0
|
前端开发
第31/90步《前端篇》第8章 重构记分板、背景、页面和游戏对象 第22课
今天学习《前端篇》第8章 重构记分板、背景、页面和游戏对象 第22课 创建记分板模块,这节课我们将实现记分板对象的模块化。目前,游戏中还没有记分板对象,在屏幕下方只有两个分数,一个是用户的,一个是系统的,这节课将仿照挡板的设计方法创建一个记分板基类,然后再派生出两个子类,即用户记分板与系统记分板。
69 0