在现在聊天表情包满天飞的当下,聊天过程当中想发送个表情感慨一下情绪在所不免,当下我就遇到这么个需求,但愿在web端聊天室中能够发送表情,还得在web端、微信H五、app端、微信公众号里都可以正常显示出来css
看到这个需求个人心里是这样的html
一番Google下来发现网上的大多都是移动端发送,以字典的方式匹配替换后web端只是单纯的作显示而已,难以找出符合我需求的文章了,那没办法,产品是老大,只能本身研究研究咯。。。node
心痒痒的话就先看看效果:click megit
完整demo在这儿:click megithub
最开始的设计思路是既然emoji表情有对应的Unicode码,那么是否是能够直接就使用Unicode码来进行传输及显示,直接不作任何处理,基于各平台对Unicode的支持,让其自生自灭呢?想着就干,尝试一波再说。。。web
看到这里若是真的动手去尝试了么估计你是真的没有好好了解一波emoji的Unicode码,只要进入官网或者各统计平台应该应该均可以看到这个东西算法
由上图能够看出个移动端对emoji的支持都存在如此大的差别,那么PC端的差距不看也知道不忍直视了,固然可能基于好奇和专研的精神,个别仍是想去尝试一波,断绝念头。。。比如如我chrome
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <style type="text/css"> .emoji-con_span { display: block; width: 300px; background-color: #cee6ff; color: #282828; border-radius: 4px; padding: 8px 12px; box-sizing: border-box; } </style> </head> <body> <span class="emoji-con_span">emoji:😂😍😘</span> </body> </html>
你能够将如上代码运行于各浏览器,你可能看到这个效果数据库
由上能够看出各端的差距主要在移动端和IE之间,特别是移动端和PC端的差距,几乎改头换面了,这不说产品,本身这关很明显都过不了,何况IE非edge模式下仍是黑白色。因此这个设计方案很明显被pass掉了json
方案一的失败之处很明显,PC端各平台兼容性差并且样式和移动端相比low的不行,最好的显示效果很明显是iOS端的emoji表情图标。基于此既然咱们不能统一使用Unicode码来显示,可是咱们却可使用它来传输和存储(不论云信、仍是数据库都能解析),因此咱们惟一须要解决的问题就是显示而已,既然如此,那么设计思路就很清晰了
不管是各端怎么传输,仍是数据库存储都直接使用Unicode来进行,APP端直接使用系统自带的emoji支持来显示,PC端则使用切图将Unicode正则匹配替换来显示,即只须要作一个字典来进行正则替换就OK
有兴趣不防撸撸代码尝试一下:
<template> <div id="chat" class="chat_page_con"> <!-- 输入框 --> <div>Ctrl + Enter 发送消息:</div> <div id="charInput" @click="saveRangeLocal" @focus="saveRangeLocal" @keyup="inputSend" @input="saveRangeLocal" class="chatframe_input_con scrollbar" contenteditable="true"> </div> <!-- 表情选择器 --> <div class="chatframe-icon"> <el-popover placement="bottom-start" width="400" trigger="click"> <el-tabs tab-position="bottom" value="Emotions" class="emoji_tabs_box"> <el-tab-pane v-for="(emojiCon, emojiKey, eInd) in emojiIcon" :key="emojiKey" :label="emojiKey" :name="emojiKey" > <!-- 这里的label icon不能放到json配置文件中,由于icon放到配置文件中后没法渲染出来 --> <!-- 这里很low能够本身修改,试用图片来替换,使用一样的图片加载方法保存在配置中 --> <span v-if="eInd == 0" slot="label" class="iconfont emoji_pane_tab"></span> <span v-if="eInd == 1" slot="label" class="iconfont emoji_pane_tab"></span> <span v-if="eInd == 2" slot="label" class="iconfont emoji_pane_tab"></span> <span v-if="eInd == 3" slot="label" class="iconfont emoji_pane_tab"></span> <img v-for="(emoItem, emoInd) in emojiCon" :key="emoInd" :src="getIconPic(emoItem.unicode)" alt="X" @click="sendEmojiIcon(emoItem.unicode)" class="chat_emoji_item"> </el-tab-pane> </el-tabs> <el-button class="iconfont open_emoji_icon" type="text" slot="reference"> </el-button> </el-popover> </div> <!-- 显示内容区 --> <div>发送的消息:</div> <div class="chatframe-text text_emoji" v-html="changeEmojiCon(sendValue)"></div> </div> </template>
页面代码设计相对简单,能够根据本身需求选取,重点是接下来的JS方法解读(这里只列出个别重要一点的方法作解释,完整demo自寻仓库取)
data () { return { emojiIcon: emojiData.icon, // 导入的emoji表情配置文件内容 emojiPath: new Map(), // emoji表情地址map对象, inputRange: '', // 光标 sendValue: '', // 发出的内容 } }
这其中涉及到一个字段为inputRange(光标),这里解释一下,由于插入emoji表情时须要动态的往用户编辑的当前光标处去插入emoji,因此无论用户是输入仍是点击都须要获取一下当前光标,而后将其记录下来,当点击emoji表情是就将其插入到记录的光标处
// 延时记录光标到位置 saveRangeLocal () { setTimeout(() => { this.inputRange = window.getSelection().getRangeAt(0) }, 0) }
获取光标保存时并不能直接就实时获取,不然会因此报错或undefined,因此这里使用宏任务来进行延时获取
// 点击表情,将表情添加到输入框 sendEmojiIcon (code) { let inputNode = document.getElementById('charInput') let html = "<img src='"+ this.getIconPic(code) +"' unicode = '" + code + "' alt='' >" let sel = window.getSelection() let range = this.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表情时都是传入Unicode值,字典匹配,插入对应的表情图片,再插入的image标签上添加一个Unicode属性,用于解析时字典对比替换
// 将输入框中的图片替换为emoji表情 formatInputCon () { let inputValue = document.getElementById('charInput').innerHTML inputValue = inputValue.replace(/<img.*?(?:>|\/>)/gi, (val) => { let unicode = val.match(/unicode=[\'\"]?([^\'\"]*)[\'\"]?/i)[1] let icon = this.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 }) return inputValue }
// 发送消息 inputSend (e) { if ((e.ctrlKey && e.keyCode == 13) || (e.ctrlKey && e.keyCode == 108)) { // 提交的时候使用正则替换,将br换位换行符,不能使用innertext转,有兼容性问题 this.sendValue = this.formatInputCon().replace(/<br>/g, '\r\n') } }
发送消息以前须要将输入框中的Emoji图片转换为相应的Emoji的Unicode值,同时因为使用的是富文本编辑器,因此拿到的内容中换行都是以‘br’标签来实现的,转换文本时须要替换为’\r\n‘
// 将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()) return "<img src='"+ this.getIconPic(unicode) +"'/>" }) return str }
拿到消息体时须要将其中的Emoji的Unicode码值转换为图片,最后以InnerHTML的方式插入到显示器