一:前言
近期需要接到一个需求,需要在输入框中实现@通知用户的功能,这个功能现在也有很多应用都有,像我们常用的QQ空间,微博这些,开始看到这个需求,心里一阵惊恐,没做过啊~~
二:思路
我们大体的思路就是:当监听到用户输入@的时候我们弹出人员选择器,这时候我们需要记住现在光标所在的位置,当用户选择人员完毕之后,我们创建一个span标签在插入到我们刚刚记录光标的位置,并且把我们输入的@删除,将光标放在这个节点的最后。
三:需求拆解
按住shift + @ 的时候,弹出人员选择器
人员选择器要跟随光标的位置出现
选择时 @的用户标签插入当前的光标位置中
生成@的用户标签的规则是:高亮、携带用户ID、一键删除信息、不可以编辑。
文本框要随内容自适应高度
用户点击生成的标签或移动键盘方向键也不能聚焦进@的标签,光标需自定移到当前标签最后
输入@后连续输入的非空内容作为搜索关键词
四:准备工作
普通文本输入框实现不了这个功能,这里利用了wangEditor的富文本编辑器功能作为基础载体,
wangeditor的官方文档:https://www.wangeditor.com/doc/
1:wangeditor安装:
2:使用
引入
3:初始化编辑器
这里通过各种属性来设置编辑器的基础功能
五:@功能的实现
@基础功能实现
编辑器基本环境好了后我们正式开始实现@功能,首先我们监听键盘事件:按住shift + @ 的时候,弹出人员选择框,这里监听的触发的时候需要注意的点是不同输入模式下,键盘上@符号的keyCode数字不一样,在英文模式下keyCode的值是50,而中文输入法下标点符号keyCode都是一样的:229,这里需要注意。
记录光标的位置
这里记录光标的位置是为了再触发@的时候,下拉框定位到当期@出现的位置,因此,当我们出入@触发的时候,就记录当前光标所在的坐标位置,以及当前光标所在的为本位置。这里光标的像素位置我们用的插件 caret-pos 来获取,caret-pos插件使用也很简单,直接npm安装即可这里不详细介绍。
记录光标的坐标
这里下拉框出现的位置会受到页面高度的影响,因此,如果整体的页面如果有滚动条,我们就需要通过计算滚动条的位置来设置弹窗出现的位置,这里的细节就是当输入的文字靠近输入框最右侧的时候,我们需要把下拉框定位到光标的左侧显示,这样页面就不会被挤压变形。
保存当前光标的文本位置
我们还需要记录光标在文本的位置,因为我们选中人员的时候,需要将内容填充当刚才光标的位置,保存方式也很简单,我们只需要获取当前光标所在的选区,里面包含了光标所在的各种信息,用一个全局变量保存即可。
getSelection()表示用户选择的文本范围或光标的当前位置
getRangeAt(0)表示获取当前的第一个选区
这里面有很多Selection跟Range的详细介绍以及使用可以参考文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Selection
@的功能的监听
键盘的@字符英文的code是50,还有判断同时是否按住shift + @键,而这里需要注意的点是,中文输入法下,标点符号的keyCode都是一样的,都是229,这里最好使用event.code或者event.key来作为输入是否是@的判断条件
生成 @的标签,并且高亮、携带用户ID。
生成@的用户标签的规则是:高亮、携带用户id跟userCode、一键删除信息、不可以编辑。生成逻辑也很简单,就是创建一个span标签,插入到光标的位置,然后删除用户输入的文本内容。
@内容搜索
在@触发后,用户还可以继续输入搜索内容,输入空格或者回车关闭选择框,实现方式就是监听文本内容,当我们触发@的时候getWord标识为true,然后截取用户输入的内容作为搜索关键词,而当用户输入空格或者tab键的时候我们关闭选择器,用户敲击回车的时候我们默认取搜索结果的第一条数据。
删除整块带有@内容标签,
因为我们在生成@内容的时候contentEditable属性我们没有设置成false,所以此时的整块标签其实是可以编辑的,这样显然不行,我们删除的时候要删除一整块,实现的方法就是当我们删除的内容包含到@内容的时候,我们需要手动取删除整块标签。
删除的方法就是在我们删除内容的时候,取获取当前光标的位置,判断删除的内容是否是我们设置的标识className,如果是,我们就将整个选区扩大,包含整个@内容,然后删除其节点。range.deleteContents()方法是删除节点的文本内容,我们通过range.cloneContents()这个方法获取节点,然后将其节点也删除。
移动光标位置
当我们讲鼠标放到@的内容上时,我们光标要不可聚焦上去,要定位在当前点击的@内容的末尾处,这个功能就是移动光标位置,当我们点击@内容的时候,我们需要判断光标在此节点文本中的位置,以及这个节点的文本有多长,由此可以计算,我们需要向左或者向右移动多少个单位。
selection.modify(‘move’, ‘left’, ‘character’)方法就是移动光标的位置,这个方法接受三个参数,第一个参数是移动还是扩大选区
传入
第二个是移动的方向,调整选区的方向。你可以传入
第三个参数是单位,调整的距离颗粒度。可选值有
我们这里选择的是以字符来移动。
判断空格
这里需要注意的是,判断是否输入的是空格我们不能单单使用一个空格取判断,我们在生成的时候空格是用的是 来生成的,这里我们调试发现,跟普通的空格是有区别的,**普通空格的ASCII码是32,这里富文本的空格ASCII码是160 (不间断空格:就是页面上的 ‘& nbsp’ 所产生的空格。)。**下面这个方法就是判断空格
粘贴除去样式:
原本这里wangEditor编辑器有个可以控制粘贴样式的过滤。但是测试过后这个属性并不能生效,因此我们需要自己定义粘贴事件。
这里编辑器在粘贴的时候会触发一个粘贴事件,里面会传递给我们粘贴的内容,我只需将内容的标签样式剔除,获取到里面的文本即可。
六:总结
1、在生成@的标签时,记录光标位置的时机要在键盘抬起时候记录,这时候@已经生成,如果在键盘按下瞬间去记录会导致最终@标签回填的位置总是相差一个单位。
2、普通空格跟‘ ’的ASCII码不一致,导致调试期间,判断是否为空格的时候出错,普通键盘输入的空格是ASCII32,而& nbsp’ 生成的空格ASCII码是160 ,也叫不间断空格。
3、键盘的@字符英文的code是50,中文输入法下,标点符号的keyCode都是一样的,都是229,这里在触发@的条件时候容易忽略,这里最好使用event.code或者event.key来作为输入是否是@的判断条件。
4、PC端由于发帖跟页面列表是在同一个页面,因此在我们切换页面的时候,要记得弹出框的关闭(方案:监听路由),存储光标选区信息的时候不能跨页面缓存,这样会搞垮整个页面。不然两个页面之间的编辑器会互相干扰。
4、多看文档!!!还要看官方文档,百度的有时候不靠谱,光标相关的事件以及api要熟悉,开始的时候删除光标的文本一直找不到方法,多看文档后发现有办法实现,range.cloneContents()可以拿到光标选区的节点(这里其实也只是复制克隆的节点)。还有就是移动光标位置的方法: selection.modify(‘move’, ‘right’, ‘character’)(上文有介绍使用),当时看别百度的介绍使用的时候一脸懵,感觉好难啊!但是看官方文档后豁然开朗,So easy!