在現在聊天表情包滿天飛的當下,聊天過程當中想發送個表情感慨一下情緒在所不免,當下我就遇到這麼個需求,但願在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的方式插入到顯示器