一、 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.TextAreastyle={{ 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').innerHTMLinputValue = inputValue.replace(/<img.*?(?:>|\/>)/gi, (val) => {let unicode = val.match(/unicode=[\'\"]?([^\'\"]*)[\'\"]?/i)[1]let icon = this.state.emojiIconlet iPic = ''// 遍历查找Unicode表情for (const key in icon) {if (icon.hasOwnProperty(key)) {const iType = icon[key]let flag = falsefor (let index = 0; index < iType.length; index++) {const element = iType[index]if (element.unicode == unicode) {iPic = element.emojiflag = truebreak}}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)) {vararr = 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.inputRangelet el = document.createElement("div")let frag = document.createDocumentFragment(), node, lastNodeif (!inputNode) {return}if (!range) {inputNode.focus()range = window.getSelection().getRangeAt(0)}range.deleteContents()el.innerHTML = htmlwhile ((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, codeif (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 }}><divid="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" }}></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" }}></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" }}></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" }}></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

emoji-mart或者emoji-picker-react实现一个类似于微信聊天的在线发送表情,再加一个带源码的纯js实现,emoji图片本地加载相关推荐

  1. 用html+css做出一个地下城游戏网站页面,新人练手推荐,带源码

    如果你是一名DNF爱好者,恰好你又想学习做网站,那这个例子绝对是你不可错过的练习. 先上页面效果截图: 页面代码如下: <!DOCTYPE html> <html lang=&quo ...

  2. 好爽 java_Intellij是进行scala开发的一个非常好用的工具,可以非常轻松查看scala源码,当然用它来开发Java也是很爽的,之前一直在用scala ide和ec...

    Intellij是进行scala开发的一个非常好用的工具,可以非常轻松查看scala源码,当然用它来开发Java也是很爽的,之前一直在用scala ide和eclipse,现在换成intellij简直 ...

  3. 用vb编制一个计算机程序,VB程序题:编一模拟袖珍计算器的完整程序,界面如下图所示。要求:输入两个操作数和一个操作符,根据操作符决定所做的运算。 VB源码 龚沛曾...

    VB程序题:编一模拟袖珍计算器的完整程序,界面如下图所示.要求:输入两个操作数和一个操作符,根据操作符决定所做的运算. VB源码 龚沛曾 提示: 1.为了程序运行正确,对存入操作符的文本框Text3, ...

  4. android 播放器封装,Android视频播放最全总结:MediaPlayer+TextureView封装一个完美实现全屏、小窗视频播放器,附项目源码...

    原标题:Android视频播放最全总结:MediaPlayer+TextureView封装一个完美实现全屏.小窗视频播放器,附项目源码 作者:xiaoyanger 来源:http://www.jian ...

  5. React Native调用Android原生代码实现车牌识别功能【附效果图附源码】

    转载请注明出处,原文地址:http://blog.csdn.net/lucherr/article/details/71908180 这段时间研究了下React Native,Facebook推出的, ...

  6. 拼团小程序源码_纯小白如何做一个摄影电商拼团小程序?

    过去,摄影行业都是线下实体店为主,宣传手段单一.推广效率差,客户复购率更是不怎样.随着微信小程序的出现,各个摄影行业商家开始通过小程序来进行推广,连接线上线下场景,这样便能触达更多消费者. 小程序拥有 ...

  7. HTML5一个浪漫的程序猿:3D旋转爱心表白神器思路源码视频

    源码/视频评论后加前端学习群470593776 javascript课题:3D旋转爱心表白神器 知识点:CSS3变换.3D场景运用,3D立方体制作技巧,爱心制作技巧, 原生js DOM操作,逻辑思维锻 ...

  8. 一个经典实例理解继承与多态原理与优点(附源码)---面向对象继承和多态性理解得不够深刻的同学请进...

    一 引子 都说面向对象的4大支柱是抽象,封装,继承与多态.但是一些初涉编程的开发人员,体会不到继承与多态的妙用,本文就试以一个经典实例来诠释继承与多态的用武之地.本实例的需求来自<重构>一 ...

  9. python制作桌面小程序_微信小程序在线制作:快速生成一个餐饮小程序

    相信很多餐饮行业的商家都遇到过这类问题:一到用餐高峰期,餐厅内就拥挤不堪,工作人员也忙得不可开交,前厅后厨来回跑,时间长了顾客也等得不耐烦.此外,外卖平台的高额抽成也让普通餐饮商家生存越来越艰难.本来 ...

最新文章

  1. sbt+Scala IDE建立Scala项目
  2. : Android之linux基础教学之三 分页机制
  3. XCTF-Reverse:game(涉及异或脚本编写)
  4. javascript数组中数字和非数字下标的区别
  5. recipe for target 'aclocal.m4' failed
  6. 操作系统---进程篇
  7. jquery+easyui+datagrid 排序
  8. js控制按钮n秒后可用
  9. [导入]设置wap服务器
  10. myeclipse使用步骤总结
  11. ORACLE之常用FAQ V1.0二(构架系统) (1)
  12. 如何把html文件转化为mp3,如何将Flash转换为MP3
  13. 瑞利信道的多普勒谱的原理与MATLAB仿真
  14. caj打开文件错误验证服务器,CAJ 打开pdf文件错误(无法读取交叉引用表)的解决方法...
  15. 谷歌驱动的下载和配置
  16. 8K摄像机研发之路有多难?一起了解你不知道的首款国产8K小型化广播级摄像机背后的故事
  17. qq计算机无法启动程序丢失MS,Win10无法运行QQ怎么回事 QQ提示缺少MSVCR100.dll解决方法...
  18. LeetCode 11.Container With Most Water
  19. HTML5树叶飘落动画
  20. oracle中LOB字段相关概念(自动创建LOB索引段和重建索引方法)

热门文章

  1. 嵌入式商业智能软件Wyn Enterprise正式步入V5.0时代!
  2. 全面了解Mysql(五)表
  3. 电子学会2022年9月青少年软件编程(图形化)等级考试试卷(二级)答案解析
  4. 保险Insurance
  5. js获取当天0点时间戳
  6. 数据赋能 兴业惠民 | 山东省第三届数据应用创新创业大赛潍坊分赛场火热报名中
  7. win10设置开机启动项_华硕主板如何设置开机第一启动项方法大全
  8. 数据结构 - 1 顺序表
  9. python多级雷达图绘制解析_Python实例15:霍兰德人格分析雷达图
  10. 与武汉和黄州相关的几首诗词赋