• 怎样实现网页端im即时通讯中的@人功能


    第一次使用@人功能到现在已经有差不多10年了,初次使用是通过微博体验的。@人的功能现在遍布各种应用,基本上涉及社交(IM、微博)、办公(钉钉、企业微信)等场景,就是一个必不可少的功能。

     

    最近正好在调研 IM 各种功能的技术实现方案,所以也详细地了解了下@人功能在Web网页前端的技术实现,正好借此机会给大家分享一下我所掌握的技术原理和代码实现。

    微博的实现比较简单,就是通过正则匹配,最后用空格表示匹配结束,所以实现上是直接使用了textarea标签。

    但是这个实现必须依赖的一个事情是:用户名必须唯一。

    微博的用户名就是唯一的,所以正则所匹配到的ID,一般的可以映射到唯一的一个用户上(除非ID不存在)。不过,微博中的这个功能整体输出比较宽松,你可以构造任何不存在的ID进行@操作。

    通过分析业内的主流实现,@人功能的技术实现思路大致如下:

        1)监听用户输入,匹配用户以@开头的文字;

        2)调用搜索弹窗,展示搜索出来的用户列表;

        3)监听上、下、回车键控制列表选择,监听ESC键关闭搜索弹窗;

        4)选择需要@的用户,把对应的HTML文本替换到原文本上,在HTML文本上添加用户的元数据。

    一般来说,如果像平常用的Lark搜索(Lark就是“飞书”),我们是不会通过唯一的『工号』去进行搜索,而是通过名字,但是名字会出现重复,所以就不太适合用textarea的方式,而是用contenteditable,把@文本替换成HTML标签特殊化标记。

    代码实现第1步:获得用户的光标位置

    想要获得用户输入的字符串,然后替换进去,第一步就是需要获得用户所在的光标。要获取光标信息,那就要先了解什么是『选择(Selection) 』和『范围(Range) 』。

    范围(Range)

    Range本质上是一对“边界点”:范围起点和范围终点。

    每个点都被表示为一个带有相对于起点的相对偏移(offset)的父 DOM 节点。如果父节点是元素节点,则偏移量是子节点的编号,对于文本节点,则是文本中的位置。即时通讯开发

     

    例如:

        let range = newRange();

    然后使用 range.setStart(node, offset) 和 range.setEnd(node, offset) 来设置选择边界。

    假设 HTML 片段是这样的:

        <pid="p">Example: <i>italic</i> and <b>bold</b></p>

    解释一下:

        1)range.setStart(p, 0) :将起点设置为 <p> 的第 0 个子节点(即文本节点 "Example: ");

        2)range.setEnd(p, 2) : 覆盖范围至(但不包括)<p> 的第 2 个子节点(即文本节点 " and ",但由于不包括末节点,所以最后选择的节点是 <i>)。

    我们需要创建一个范围:

        1)从的第一个子节点的位置 2 开始(选择 "Example: " 中除前两个字母外的所有字母);

        2)到 的第一个子节点的位置 3 结束(选择 “bold” 的前三个字母,就这些),代码如下。

    选择(Selection)

    Range 是用于管理选择范围的通用对象。

    文档选择是由 Selection 对象表示的,可通过 window.getSelection() 或 document.getSelection() 来获取。

    根据 Selection API 规范:一个选择可以包括零个或多个范围(不过实际上,只有 Firefox 允许使用 Ctrl+click (Mac 上用 Cmd+click) 在文档中选择多个范围)。

    其他浏览器最多支持 1 个范围。

    正如我们将看到的,某些 Selection 方法暗示可能有多个范围,但同样,在除 Firefox 之外的所有浏览器中,范围最多是 1。

    与范围相似,选择的起点称为“锚点(anchor)”,终点称为“焦点(focus)”。

    主要的选择属性有:

        1)anchorNode:选择的起始节点;

        2)anchorOffset:选择开始的 anchorNode 中的偏移量;

        3)focusNode:选择的结束节点;

        4)focusOffset:选择开始处 focusNode 的偏移量;

        5)isCollapsed:如果未选择任何内容(空范围)或不存在,则为 true ;

        6)rangeCount:选择中的范围数,除 Firefox 外,其他浏览器最多为 1。

    看完上面,不知道了解了没?没关系,我们继续往下。

    综上所述:一般我们只有一个 Range,当我们的光标在 contenteditable 的 div 上闪动的时候,其实就有了一个 Range,这个 Range 的开始和结束位置都是一样的。

    另外:我们还可以直接通过 Selection.focusNode获取到对应的节点,通过 Selection.focusOffset 获取到对应的偏移量。

    代码实现第2步:获取需要@的用户

    在上一节我们获得了光标在对应Node节点的偏移量,以及对应的Node节点。那么就可以通过textContent方法获取整个文本。

    Web前端富文本的坑确实比较多,之前没怎么了解过这部分的知识。虽然整个过程看起来很粗糙,但是技术原理就是这样。

    不完善的地方很多,有更好的方式可以共同讨论下。

  • 相关阅读:
    vue三个点…运算符时报错 Syntax Error: Unexpected token
    ZZNUOJ_用C语言编写程序实现1359:数独(附完整源码)
    vagrant设置磁盘大小
    C# 使用SIMD向量类型加速浮点数组求和运算(1):使用Vector4、Vector<T>
    嗦嗦postMessage和webSocket
    读取pdf、docx、doc、ppt、pptx并转为txt
    代码随想录算法训练营二十四期第十三天|LeetCode239. 滑动窗口最大值、LeetCode347. 前 K 个高频元素
    WMS系统4.0,仓库管理的20年历史变局你知道吗?
    低代码与国产化部署:软件开发的未来趋势与应用实践
    蓝桥杯C/C++省赛:排它平方数
  • 原文地址:https://blog.csdn.net/wecloud1314/article/details/125408993