emoji-mart或者emoji-picker-react实现一个类似于微信聊天的在线发送表情,再加一个带源码的纯js实现,emoji图片本地加载
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";im
一、 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(/ /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" }}
></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
更多推荐
所有评论(0)