在 Web 中实现表情符号的输入,小白也能看明白

了解了Selection和Range的基础知识后,我们继续来完成微博风格的表情输入。前面说过,要点是表情HTML代码的插入位置要符合输入框的光标位置,所以我们首先要做的就是记录这个光标位置。

先标记输入框为inputBox(本示例使用Vue):

ref=“inputBox”

class=“input-box”

contenteditable=“true”>

然后使用前面提到的document.onselectionchange监听选择变化事件:

document.onselectionchange = () => {

let selection = document.getSelection();

if (selection.rangeCount > 0) {

const range = selection.getRangeAt(0);

if (vmEmoji.$refs.inputBox.contains(range.commonAncestorContainer)) {

rangeOfInputBox = range;

}

}

};

这段代码的作用是,在“当前选择”发生变化(鼠标点击或触摸动作等)后,如果变化后的Selection位于输入框inputBox内部,就用变量rangeOfInputBox保存它。这里也可以看到,Selection是用Range来保存的。

selection.rangeCount是Selection的属性,它表示Selection正在应用的Range数目。当它大于0时,说明当前是“有选择”的状态。

range.commonAncestorContainer是Range的属性,它表示Range的两个边界点的距离最近的共同父元素。这里用于判断Range发生在inputBox内。

最后,当点击表情时,执行插入表情的方法insertEmoji:

insertEmoji (name) {

let emojiEl = document.createElement(“img”);

emojiEl.src = ${this.emoji.path}${name}${this.emoji.suffix};

if (!rangeOfInputBox) {

rangeOfInputBox = new Range();

rangeOfInputBox.selectNodeContents(this.$refs.inputBox);

}

if (rangeOfInputBox.collapsed) {

rangeOfInputBox.insertNode(emojiEl);

} else {

rangeOfInputBox.deleteContents();

rangeOfInputBox.insertNode(emojiEl);

}

rangeOfInputBox.collapse(false);

}

这段代码中,参数name代表了不同表情,从而生成不同表情对应的不同HTML元素(都是)。

如果rangeOfInputBox不存在,说明还没有过任何发生在输入框内的选择事件,此时就指定一个默认的Range。selectNodeContents(node)是Range的方法,将一个Range设定为选中整个node元素内容。

insertNode(node)是Range的方法,可以将node元素插入到Range的起始边界点。它是本示例的关键方法,用于完成表情HTML元素插入。这里需要对Range的状态做判断,如果Range是折叠的(闪烁光标),直接插入表情元素,如果Range不是折叠的(选中了一部分输入框内容),就先删除选中的内容,再插入表情元素(相当于替换内容的效果)。deleteContent()也是Range的方法,可以将Range包含的内容从网页文档中删除。

结尾调用的collapse(toStart)仍然是Range的方法,它可以将Range的两个边界点变成相同的,也就是折叠的状态。如果参数toStart为true则取起始边界点的位置,如果为false则是取结尾边界点。这里取的是结尾边界点,这样就好像是在插入一个表情后,自动将光标移动到刚插入的表情元素后方,从而支持表情的连续输入。

到此,微博风格的表情输入就已经实现了:

把输入框内的内容作为HTML代码(富文本),就可以提交给后台,或者像图里这样简单展示在上方的聊天窗口内。

完善点击表情时的光标置位

这种文字和表情图混合在一起的风格还存在一个待完善的地方:如果点击文字,光标会正确定位到选中的文字前方,而点击表情图,就没有任何动作。这个光标置位的功能我们可以手动补全。

为输入框增加click事件处理:

ref=“inputBox”

@click=“handleBoxClick”

class=“input-box”

contenteditable=“true”>

对应的handleBoxClick()事件处理方法如下:

handleBoxClick (event) {

let target = event.target;

this.setCaretForEmoji(target);

},

setCaretForEmoji (target) {

if (target.tagName.toLowerCase() === “img”) {

let range = new Range();

range.setStartBefore(target);

range.collapse(true);

document.getSelection().removeAllRanges();

document.getSelection().addRange(range);

}

},

setStartBefore(node)是Range的方法,可以设定边界起始点的位置到一个元素之前。这段代码整体来说就是,如果当前click的是元素,就创建一个Range,设定它为折叠状态,位置在刚才点击的表情图之前,然后应用这个Range到Selection,变成真实可见的选择效果。


用纯文本符号来替代表情的场景


现在,我们重新开始,来实现微信风格的表情输入。

前面说过,微信是使用类似[旺柴]这样的符号标识来替代表情的风格。这种风格全部使用纯文本,因此,输入框会很容易实现,可以直接使用表单元素的文本输入框:

<input

ref=“formInput”

@keydown=“handleFormInputKeydown”

class=“form-input”

type=“text”>

这里预留的handleFormInputKeydown()输入事件处理方法,将在后文中使用。

和微博风格类似,接下来也是可以分成两个实现要点:

  • 点击下方的表情,就将该表情对应的纯文本符号插入到输入框。

  • 纯文本符号的插入位置要符合输入框的当前光标位置。

虽然同样是结合Selection和Range的概念,按光标位置来插入纯文本符号,但会更加简单。

按光标位置来插入纯文本

表单元素自身有以下3个属性是关于“选择”的:

  • input.selectionStart - 选择的起始位置。它的值是一个索引数字,比如6。

  • input.selectionEnd - 选择的结尾位置。值的格式同上。

  • input.selectionDirection - 选择的方向。可选值"forward",“backward"和"none”。一般对应的情况是指鼠标拖拽选择时是从前向后,还是从后向前,又或者是双击选中。

通过这些属性,就可以实现对“选择”状态的读取和写入,而无需使用Selection和Range。

现在,点击表情时,执行插入表情的方法insertEmojiText:

insertEmojiText (name) {

let input = this.$refs.formInput;

let emojiText = [${name}];

input.focus();

input.setRangeText(emojiText, input.selectionStart, input.selectionEnd, “end”);

input.blur();

}

可以看到纯文本的表情插入非常简单。这里也是用[name]的符号来表示表情。

input.setRangeText(replacement, [start], [end], [selectionMode])是input的方法,可以将索引位置从start到end的文本,替换成replacement的文本。而如果start等于end,就相当于闪烁光标的状态,没有文本会被替换,变成了插入文本的效果。末尾参数selectionMode决定了在文本替换(或插入)操作完毕后,input如何更新选择状态。这里取"end"表示将选择状态设定为“闪烁光标,位置在新插入文本的后方”,从而支持表情连续输入。

使用input.setRangeText(),无论当前状态是闪烁光标,还是已经选择了一些文本,都会以符合我们输入习惯的方式插入表情文本。

关于input.setRangeText()的更详细的说明,同样推荐阅读这篇Selection And Range。

这段代码中的input.focus()和input.blur(),是因为仅在元素被focus的情况下进行文本编辑操作,才能确保input.selectionStart和input.selectionEnd两个值正确更新。同时,这里又并不希望元素被真地focus,所以又用了input.blur()来取消。

到这里,微信风格的表情输入就基本可用了。但是,这种纯文本符号的风格也有一个应完善的地方:用退格键(Backspace)来删除文本时,代表一个表情的纯文本符号应该以作为一个整体被删除。比如[旺柴]这样的表情符号,在光标位于]的后方时,一个退格键就应该删除这一整段文本。这也是微信里存在的功能。

退格键支持 - 以表情符号为整体删除文本

前文示例中为元素预留的handleFormInputKeydown()方法,就是用于实现这一功能:

handleFormInputKeydown (event) {

let input = this.$refs.formInput;

let chatString = input.value;

// “Backspace” and selection type “Caret”

if (event.keyCode === 8 && input.selectionStart === input.selectionEnd) {

let indexEnd = input.selectionStart - 1;

let charToDelete = chatString.charAt(indexEnd);

// delete the whole [***]

if (charToDelete === “]”) {

event.preventDefault();

let indexStart = chatString.lastIndexOf(“[”, indexEnd);

input.setRangeText(“”, indexStart, indexEnd + 1, “end”);

}

}

}

这段代码是判断当选择状态为闪烁光标,且刚好位于字符]后按下了退格键的时候,就找出整个[name]表情文本,使用input.setRangeText()实现整段删除。

到此,微信风格的表情输入也就完成了:

在提交给后台或者图中这样展示在上方聊天窗口内的时候,取输入框内的纯文本,然后将所有[name]格式的文本符号,替换成对应表情的HTML(比如[1]变成)即可。


完整代码示例


两种风格的完整代码示例:

  • 微博风格(表情图和文字一起)

  • https://codesandbox.io/s/emoji-input-contenteditable-75qe8

  • 微信风格(表情用纯文本符号替代)

  • https://codesandbox.io/s/emoji-input-text-yqfe3



补充


光标颜色

Selection在可输入元素内的折叠状态,也就是闪烁光标,它的颜色也是可以修改的,比如:

input {

caret-color: red;

}

会将闪烁光标修改为红色。更详细的说明请查看MDN上的caret-color。

输入法里的表情字符

在手机上,你可能注意到像搜狗这样的输入法也给你提供了一套表情(上图中的Emoji),它们在微信中也可以使用,而且可以直接显示在微信的输入框内。这种不依赖其他东西就可以使用的表情,本质上是Unicode字符,你可以到Unicode Character Table上查找更多的表情字符。

Unicode字符表情最终呈现的样子取决于它所处的环境。比如不同手机,不同操作系统,都可能有不同的外观。

定义虚拟键盘的动作键

手机上的输入法键盘,右下角的动作键可以通过HTML属性enterkeyhint设置为不同的类型:

ref=“inputBox”

enterkeyhint=“send”

contenteditable=“true”>

这里值send对应的就是前面图中的“发送”。其他可用的值可以参考MDN上的enterkeyhint。

如果想要像微信那样,点击虚拟键盘右下角的“发送”就可以发送消息(而不是点击网页上的按钮),监听输入元素的键盘事件,并确认按键为enter键即可。


自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

javascript是前端必要掌握的真正算得上是编程语言的语言,学会灵活运用javascript,将对以后学习工作有非常大的帮助。掌握它最重要的首先是学习好基础知识,而后通过不断的实战来提升我们的编程技巧和逻辑思维。这一块学习是持续的,直到我们真正掌握它并且能够灵活运用它。如果最开始学习一两遍之后,发现暂时没有提升的空间,我们可以暂时放一放。继续下面的学习,javascript贯穿我们前端工作中,在之后的学习实现里也会遇到和锻炼到。真正学习起来并不难理解,关键是灵活运用。

资料领取方式:点击这里免费领取前端全套学习资料

css源码pdf

JavaScript知识点
**

[外链图片转存中…(img-cZBD9QXY-1712323006008)]

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

javascript是前端必要掌握的真正算得上是编程语言的语言,学会灵活运用javascript,将对以后学习工作有非常大的帮助。掌握它最重要的首先是学习好基础知识,而后通过不断的实战来提升我们的编程技巧和逻辑思维。这一块学习是持续的,直到我们真正掌握它并且能够灵活运用它。如果最开始学习一两遍之后,发现暂时没有提升的空间,我们可以暂时放一放。继续下面的学习,javascript贯穿我们前端工作中,在之后的学习实现里也会遇到和锻炼到。真正学习起来并不难理解,关键是灵活运用。

资料领取方式:点击这里免费领取前端全套学习资料

[外链图片转存中…(img-jVX9Hvbe-1712323006008)]

[外链图片转存中…(img-v3IK86Tm-1712323006008)]

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值