玩命加载中 . . .

项目功能实现:评论框插入表情


问题

一个 React 项目(用的函数组件)需要支持可以回复表情的评论,看到这个需求,相信读者们能想到好几种解决方案,比如使用成熟的富文本编辑器、使用第三方表情组件库、自己封装组件……

考虑到开发成本不能太高、评论组件可以复用、需求只是支持表情即可,因此笔者选择了第二种解决方案,也就是说,要去技术调研以选择合适的第三方库。

技术选型

GitHub 上有很多开源的表情库,对比了一下,个人觉得 emoji-mart 是最好的一个,原因:

  1. star 数达到了 5.4 k
  2. 使用文档清晰易懂,支持中文;
  3. 作者持续维护,可以看到上一次的 commit 时间是 5天 前;
  4. UI 设计高级,符合笔者审美。

到这里,就确定了技术选型(出来吧 emoji-mart 就决定是你了hhh)

实践

安装表情组件库

我们首先使用 npm install --save emoji-mart @emoji-mart/data 将库安装下来。

生成容器组件

然后把 demo 跑起来,这里新建了一个 EmojiPicker.js 文件,用于生成容器组件,其代码如下:

import React, { useEffect, useRef } from 'react';

import data from '@emoji-mart/data';
import { Picker } from 'emoji-mart';

function EmojiPicker(props) {
  const ref = useRef();

  useEffect(() => {
    new Picker({ ...props, data, ref });
  }, []);

  return <div ref={ref} />;
}

export default EmojiPicker;

容器组件生成之后,就可以在其他地方使用,并调用它的 API 。在上面这段代码中,是不能在 div 中调用诸如 onEmojiSelect 之类的 API 的,因为此时容器组件还没生成,div 还仅仅只是 div 而已,这也是为什么要新建一个 EmojiPicker.js 的原因。

调用容器组件

在评论框处调用 EmojiPicker ,即可在页面中看到效果了。

<EmojiPicker
  onEmojiSelect={emoji => searchEmoji(emoji)}
/>

这里只展示与表情组件库有关联的输入框,其他相关组件的布局这里就不一一写出来了,请读者自由发挥
<Input.TextArea
  id='textarea'
  value={chatContent} // 输入框文本值由 chatContent 决定,因此要维护 chatContent
  onChange={e => onChatContentChange(e.target.value)} // 文本值发生改变时的回调
/>

到这里,我们离成功实现评论功能迈进了一小步,但还不够,可以看到上面代码有三个动态数据,分别是一个 chatContent 字段、onChatContentChange 函数和 searchEmoji 函数,我们继续往(写)下(bug)。

封装函数

  1. 第一版

在输入框进行键盘输入和表情插入,我们可以想到这样的逻辑:

  • ①键盘输入时直接调用输入框的 onChange API 更新文本值 chatContent 即可
  • ②插入表情时调用组件库的 onEmojiSelect API 获取到要插入的表情,拼接到 chatContent 后面

于是有了下面的代码:

const [chatContent, setChatContent] = useState('');

// ①输入框文本值发生改变的回调函数
const onChatContentChange = (value) => {
    setChatContent(value);
};

// ②选中表情的回调函数
const searchEmoji = (emoji) => {
    // 考虑到在一开始就插入表情的情况
    const newChatContent = chatContent.length > 0 ? chatContent + emoji.native : emoji.native;
    setChatContent(newChatContent);
};

然而这版的代码是有 bug 的,假如先输入“你好”,再插入表情,预计结果应该是“你好【表情】”,然而实际结果是“【表情】”,如图所示:
输入“你好”
在“你好”后输入表情

为什么会这样呢?这是因为 chatContent.length0 ,自然就被赋值为 emoji.native 了。那么,在 @飞哥 的提点下,用 tempTextAreaData 全局字段存储输入框的文本值,这样就可以在页面更新的时候依然也能拿到最新的文本值,因此代码变成这样:

+   let tempTextAreaData = '';

const [chatContent, setChatContent] = useState(''); 

// ①输入框文本值发生改变的回调函数 
const onChatContentChange = (value) => {
    setChatContent(value);
+   tempTextAreaData = value;
};

// ②选中表情的回调函数
const searchEmoji = (emoji, event) => {
    // 考虑到在一开始就插入表情的情况 
-   const newChatContent = chatContent.length > 0 ? chatContent + emoji.native : emoji.native;
+   const newChatContent = tempTextAreaData.length > 0 ? tempTextAreaData + emoji.native : emoji.native;
+   tempTextAreaData = newChatContent;
    setChatContent(newChatContent);
};

来看看效果:

输入“你好”
在“你好”后输入表情

  1. 第二版

我们完成了第一版的代码,实现了在输入框文本值后面插入表情,但有一个缺陷:不能在文本中间插入表情,因此我们的代码逻辑需要进一步完善。

笔者的思考方向是这样的:当光标移动到文本中间时,获取这个光标的位置,截断光标之前和光标之后的字符,将表情拼接到两者之间。

onChatContentChange 函数比较简单,第一版已经搞定了,现在我们完善 searchEmoji 函数:

// 由于在光标中间拼接表情稍微复杂了一点,这里单独抽成 insertAtCursor 函数,tempTextAreaData 的更新也将在 insertAtCursor 中完成
const searchEmoji = (emoji) => {
    let dom = document.getElementById('textarea'); // 获取输入框的节点对象
    insertAtCursor(dom, emoji.native); // 将表情插入到光标后面
};

insertAtCursor 函数:


const insertAtCursor = (myField, myValue) => {
  if (document.selection) {
    //IE support
    myField.focus();
    const sel = document.selection.createRange();
    sel.text = myValue;
    sel.select();
  } else if (myField.selectionStart || myField.selectionStart == '0') {
    //MOZILLA/NETSCAPE support
    const startPos = myField.selectionStart;
    const endPos = myField.selectionEnd;
    const beforeValue = myField.value.substring(0, startPos);
    const afterValue = myField.value.substring(endPos, myField.value.length);

    tempTextAreaData = beforeValue + myValue + afterValue;
    setChatContent(tempTextAreaData);
    myField.selectionStart = startPos + myValue.length;
    myField.selectionEnd = startPos + myValue.length;
    myField.focus();
  } else {
    tempTextAreaData += myValue;
    setChatContent(tempTextAreaData);
    myField.focus();
  }
}

这样一来,就可以在文本中插入表情了,效果如图:

输入一段文字

文本中插入表情

将选取替换成表情

思考

在第二版代码完成之后,这个项目需求就完成了,当然,这样的实现是基于这个评论功能只需要支持表情就好,对于图片的插入、上传等是不支持的,如果确实需要支持图片的话,其实可以考虑富文本编辑器,工具栏简洁一点,支持图片和表情,也就可以了。

关于富文本编辑器的技术选型,可以参考这篇文章:前端开发常用的10款富文本编辑器

希望读者们能多多支持这个项目: onepiece-web(star 一下不亏的 ^_^)
以上就是全部内容了,希望对大家有所帮助~


文章作者: hcyety
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hcyety !
评论
  目录