一、 emoji-mart

import './App.css';
import React, { Component } from 'react'
import 'emoji-mart/css/emoji-mart.css'
import { Picker } from 'emoji-mart'
import { Input, Tooltip } from 'antd';
import "antd/dist/antd.css";
import { SmileOutlined } from '@ant-design/icons';
import _ from "lodash"
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      chatContent: "",
      showEmojiModal:false
    }
  }
  onChatContentChange = (value) => {
    this.setState({ chatContent: value })
  };
  searchEmoji(emoji, event) {
    console.log("emoji, event=====", emoji, event)
    this.setState({ emoji: emoji, showEmojiModal: false })
    let { chatContent } = this.state;
    chatContent = !_.isEmpty(chatContent) ? (chatContent + emoji.native) : emoji.native;
    this.setState({ chatContent });
  }
  render() {
    return (
      <div className="App">
        <div className="emoji_container">
          {this.state.showEmojiModal&&<Picker set='apple' 
            emoji=''
            showPreview={false}  
            onClick={(emoji, event)=>this.searchEmoji(emoji, event)}/>}
        </div>
        {/* emoji=''设置preview默认状态下不显示图片 */}
        <div style={{ padding: 15, paddingBottom: 10, cursor: 'pointer' }} onClick={() => this.setState({ showEmojiModal: !this.state.showEmojiModal })}>
          <Tooltip title="表情">
            <SmileOutlined style={{ fontSize: 20, color: '#787878' }} />
          </Tooltip>
        </div>
        <div style={{ display: 'flex', flex: 1, alignItems: 'center', paddingLeft: 10, paddingRight: 10, paddingBottom: 10 }}>
          <Input.TextArea
            style={{ display: 'flex', flex: 1, height: '100%', boxShadow: "none", fontSize: 16 }}
            placeholder="请输入...您可按Enter键发送消息"
            value={this.state.chatContent}
            onPressEnter={e => { }}
            onKeyUp={this.onKeyUp}
            onChange={e => this.onChatContentChange(e.target.value)}
          />
        </div>
      </div>
    );
  }
}

export default App;

 

.App{padding: 20px;}
.emoji-mart-search{display: none;}/* 隐藏搜索框 */
.emoji-mart-preview{display: none;}
/* 隐藏解釋當前點擊的表情框 */

按照如上代码

https://github.com/missive/emoji-mart/issues/465

必须对输入框的font-family进行样式控制,否则有些电脑查看,表情会变成方框

{font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji", "Twemoji Mozilla", "Noto Color Emoji", "Android Emoji", -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif!important ;}

二、emoji-picker-react

import React, { useState } from 'react';
import Picker from 'emoji-picker-react';

const App = () => {
  const [chosenEmoji, setChosenEmoji] = useState(null);

  const onEmojiClick = (event, emojiObject) => {
    setChosenEmoji(emojiObject);
  };

  return (
    <div>
      {chosenEmoji ? (
        <span>You chose: {chosenEmoji.emoji}</span>
      ) : (
        <span>No emoji Chosen</span>
      )}
      <Picker onEmojiClick={onEmojiClick} />
    </div>
  );
};
export default App;

必须对输入框的font-family进行样式控制,否则有些电脑查看,表情会变成方框

{font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Segoe UI", "Apple Color Emoji", "Twemoji Mozilla", "Noto Color Emoji", "Android Emoji", -apple-system, BlinkMacSystemFont, Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif!important ;}

手机端实现的话,比较简单,有现成的组件:aurora-imui-react-native

三、纯js实现,emoji图片从本地加载

区别于上面几个,它是将emoji以图片的形式展现出来,使用可编辑的div来替代输入框。注意:图片需要在public内也放置一份,不然img不能直接通过链接获取图片资源

Div + contenteditable去掉外框

#charInput:focus { border: none;outline:none; }

注意提交文本的时候,若是直接回车发送文案的,绑定的发送事件内,下列这行代码需要更改

 let { chatContent, messageList } = this.state;
        chatContent = this.formatInputCon().replace(/<br>/g, '\r\n')

更换成

 let { chatContent, messageList } = this.state;
        chatContent = this.formatInputCon().replace(/<br>/g, '\r\n')

还有注意,需要将内容中的div标签替换成空

切记,火狐上面兼容光标时,这个可编辑的div不能设置样式display:flex

若是给回车事件绑定了发送事件,那么在回车事件内,应该阻止一下默认事件

if (e.preventDefault) {
                  e.preventDefault()
                } else {
                  e.retuenValue = false
                }
                this.onSendText();

完整源码:https://github.com/MeiJunNa/emoji

import React, { Component } from 'react'
import { Tooltip, Button, message } from 'antd';
import { SmileOutlined } from '@ant-design/icons';
import "antd/dist/antd.css";
import _ from "lodash";
import "./sendEmoji.css";
const emojiData = require('./assets/emoji/emoji.json')
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      chatContent: "",              // 发出的内容
      showEmojiModal: false,
      emojiIcon: emojiData.icon,    // 导入的emoji表情配置文件内容
      inputRange: '',               // 光标
      showEmotions: true,
      showDingbats: false,
      showPerson: false,
      showUncategorized: false,
    }
    this.saveRangeLocal = this.saveRangeLocal.bind(this);
    this.inputSend = this.inputSend.bind(this);
  }
  // 将输入框中的图片替换为emoji表情
  formatInputCon() {
    let inputValue = document.getElementById('charInput').innerHTML
    inputValue = inputValue.replace(/<img.*?(?:>|\/>)/gi, (val) => {
      let unicode = val.match(/unicode=[\'\"]?([^\'\"]*)[\'\"]?/i)[1]
      let icon = this.state.emojiIcon
      let iPic = ''

      // 遍历查找Unicode表情
      for (const key in icon) {
        if (icon.hasOwnProperty(key)) {
          const iType = icon[key]
          let flag = false

          for (let index = 0; index < iType.length; index++) {
            const element = iType[index]

            if (element.unicode == unicode) {
              iPic = element.emoji
              flag = true
              break
            }
          }

          if (flag) { break }
        }
      }

      return iPic
    })
    console.log("inputValue", inputValue)
    this.setState({ chatContent: inputValue })
    return inputValue
  }
  // 发送消息
  inputSend(e) {
    let { chatContent, messageList } = this.state;
    chatContent = this.formatInputCon();
    // this.state.chatContent = this.formatInputCon().replace(/<br>/g, '\r\n')
    // 是否为图片
    const isImg = (/^\<img src\=/g).test(chatContent)
    let imgSrc = ''
    chatContent = chatContent.replace(/<br>/g, '')
    chatContent = chatContent.replace(/&nbsp;/g, '')
    this.setState({ showEmojiModal: false, showEmotions: true })
    //发送消息后清空输入框
    let inputValue = document.getElementById('charInput')
    inputValue.innerHTML = ""
    if (_.isEmpty(chatContent)) {
      return message.warn("请输入消息内容");
    }
    // 如果是图片 正则匹配获取src地址
    if (isImg) {
      chatContent = chatContent.replace(/^\<img src\=/, "")
      chatContent = chatContent.replace(/\>$/, "")
      chatContent = chatContent.replace(/alt=""/, "")
      chatContent = chatContent.replace(/\"|\'/g, "")
      // console.log(chatContent , '==========')
    }
    // 如果是base64图片
    // 需要上传服务器 base64格式字节太大 会导致socket断开
    if (/data:image/.test(chatContent)) {
      var
        arr = chatContent.split(','),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[1]),
        n = bstr.length,
        u8arr = new Uint8Array(n);

      while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
      }
      var obj = new Blob([u8arr], { type: mime });
      var fd = new FormData();
      fd.append("upfile", obj, "image.png");
      const xhr = new XMLHttpRequest();
      // 图片上传服务器地址
      // xhr.open("POST", (process.env.NODE_ENV === 'development' ? config.baseUrl.dev : config.baseUrl.pro) + "media/upload");
      // xhr.send(fd);
      // xhr.onreadystatechange = () => {
      //   if (xhr.readyState === 4 && xhr.status === 200) {
      //     // 获取响应文件
      //     let str = xhr.responseText
      //     // 将json解析为对象
      //     str = JSON.parse(str)
      //     // 图片读取地址
      //     imgSrc = (process.env.NODE_ENV === 'development' ? config.baseFileUrl.dev : config.baseFileUrl.pro) + str.ret

      //     let nMessage = {
      //       // 是否为图片
      //       isImg: isImg,
      //       imgSrc: imgSrc,
      //       msgId: msgId,
      //       status: "send_succeed",
      //       msgType: "text",
      //       text: isImg ? `<img src=${imgSrc} alt="" />` : chatContent,
      //     };
      //   }
      // };
    }
    else {
      let nMessage = {
        // 是否为图片
        isImg: isImg,
        // base64图片地址
        imgSrc: imgSrc,
        msgType: "text",
        text: isImg ? `<img src=${chatContent} alt="" />` : chatContent,
      };
      // if (global.isURL(chatContent)) {
      //   nMessage.type = "url";
      //   nMessage.mediaPath = chatContent;
      // }
    }
  }
  // 延时记录光标到位置
  saveRangeLocal() {
    setTimeout(() => {
      this.state.inputRange = window.getSelection().getRangeAt(0)
    }, 0)
  }
  // 点击表情,将表情添加到输入框
  selectEmojiIcon(emoji) {
    this.setState({ showEmojiModal: false, showEmotions: true })
    let inputNode = document.getElementById('charInput')
    const imgUrl = './assets/emoji/icon/' + emoji.unicode + '.png'
    let html = "<img src='" + imgUrl + "' unicode = '" + emoji.unicode + "' alt='' class='iconImgDiv'>"
    let sel = window.getSelection()
    let range = this.state.inputRange
    let el = document.createElement("div")
    let frag = document.createDocumentFragment(), node, lastNode

    if (!inputNode) {
      return
    }

    if (!range) {
      inputNode.focus()
      range = window.getSelection().getRangeAt(0)
    }

    range.deleteContents()
    el.innerHTML = html

    while ((node = el.firstChild)) {
      lastNode = frag.appendChild(node)
    }
    range.insertNode(frag)

    if (lastNode) {
      range = range.cloneRange()
      range.setStartAfter(lastNode)
      range.collapse(true)
      sel.removeAllRanges()
      sel.addRange(range)
    }
  }


  // 将emoji表情转换为图片
  changeEmojiCon(str) {
    let patt = /[\ud800-\udbff][\udc00-\udfff]/g    // 检测utf16字符正则

    str = str.replace(patt, (char) => {
      let H, L, code

      if (char.length === 2) {
        H = char.charCodeAt(0)   // 取出高位
        L = char.charCodeAt(1)   // 取出低位
        code = (H - 0xD800) * 0x400 + 0x10000 + L - 0xDC00   // 转换算法
        return "&#" + code + ";"
      } else {
        return char
      }
    })
    str = str.replace(/&#{1}[0-9]+;{1}/ig, (a) => {
      let unicode = a.replace(/^&#{1}/ig, '')
      unicode = unicode.replace(/;{1}$/ig, '')
      unicode = 'U+' + (parseFloat(unicode).toString(16).toUpperCase())
      const imgUrl = './assets/emoji/icon/' + unicode + '.png'
      return "<img src='" + imgUrl + "'/>"
    })
    return str
  }
  render() {
    return (
      <div className="App">
        <div style={{ padding: 15, paddingBottom: 10, cursor: 'pointer' }} onClick={() => this.setState({ showEmojiModal: !this.state.showEmojiModal })}>
          <Tooltip title="表情">
            <SmileOutlined style={{ fontSize: 20, color: '#787878' }} />
          </Tooltip>
        </div>
        <div style={{ display: 'flex', flex: 1, alignItems: 'center', paddingLeft: 10, paddingRight: 10, paddingBottom: 10 }}>
          <div
            id="charInput"
            style={{ display: 'flex', flex: 1, boxShadow: "none", height: '100px', border: "1px solid #eee", fontSize: 16 }}
            className="text_emojs_box"
            contentEditable="true"
            onClick={this.saveRangeLocal}
            onFocus={this.saveRangeLocal}
            onInput={this.saveRangeLocal}
            onPaste={(e)=>this.pasteEvent(e)}
          />
          <Button type="primary" style={{ marginLeft: 10 }} onClick={() => this.inputSend()}>发送</Button>
        </div>
        {this.state.showEmojiModal &&
          <div className="chatframe_input_con scrollbar">
            <div className="chatframe-icon">
              <span className="iconfont emoji_pane_tab"
                onClick={() => this.setState({ showEmotions: true, showDingbats: false, showPerson: false, showUncategorized: false })}
                style={{ color: this.state.showEmotions ? "#333" : "#ccc" }}
              >&#xe612;</span>
              <span className="iconfont emoji_pane_tab"
                onClick={() => this.setState({ showDingbats: true, showEmotions: false, showPerson: false, showUncategorized: false })}
                style={{ color: this.state.showDingbats ? "#333" : "#ccc" }}
              >&#xe609;</span>
              <span className="iconfont emoji_pane_tab"
                onClick={() => this.setState({ showPerson: true, showEmotions: false, showDingbats: false, showUncategorized: false })}
                style={{ color: this.state.showPerson ? "#333" : "#ccc" }}
              >&#xe62d;</span>
              <span className="iconfont emoji_pane_tab"
                onClick={() => this.setState({ showUncategorized: true, showEmotions: false, showDingbats: false, showPerson: false })}
                style={{ color: this.state.showUncategorized ? "#333" : "#ccc" }}
              >&#xe63c;</span>
            </div>
            <ul>
              {this.state.showEmotions && this.state.emojiIcon.Emotions.map((emotions, index) => {
                const imgUrl = './assets/emoji/icon/' + emotions.unicode + '.png'
                return (
                  <li key={"emotions" + index} className="chat_emoji_li">
                    <img src={imgUrl} className="chat_emoji_item" onClick={() => this.selectEmojiIcon(emotions)} />
                  </li>
                )
              })}
              {this.state.showDingbats && this.state.emojiIcon.Dingbats.map((emotions, index) => {
                const imgUrl = './assets/emoji/icon/' + emotions.unicode + '.png'
                return (
                  <li key={"emotions" + index} className="chat_emoji_li">
                    <img src={imgUrl} className="chat_emoji_item" onClick={() => this.selectEmojiIcon(emotions)} />
                  </li>
                )
              })}
              {this.state.showPerson && this.state.emojiIcon.Person.map((emotions, index) => {
                const imgUrl = './assets/emoji/icon/' + emotions.unicode + '.png'
                return (
                  <li key={"emotions" + index} className="chat_emoji_li">
                    <img src={imgUrl} className="chat_emoji_item" onClick={() => this.selectEmojiIcon(emotions)} />
                  </li>
                )
              })}
              {this.state.showUncategorized && this.state.emojiIcon.Uncategorized.map((emotions, index) => {
                const imgUrl = './assets/emoji/icon/' + emotions.unicode + '.png'
                return (
                  <li key={"emotions" + index} className="chat_emoji_li">
                    <img src={imgUrl} className="chat_emoji_item" onClick={() => this.selectEmojiIcon(emotions)} />
                  </li>
                )
              })}
            </ul>
          </div>}
        {/* <!-- 显示内容区 --> */}
        <div>发送的消息:</div>
        <div className="chatframe-text text_emoji" dangerouslySetInnerHTML={{ __html: this.changeEmojiCon(this.state.chatContent) }}></div>
      </div>
    )
  }
}

export default App;

Vue可参考这位大佬的代码https://github.com/zhazhanitian/Emoji-ChatRoom

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐