序言
在如今聊天表情包滿天飛的當下,聊天過程中想發送個表情感慨一下情緒在所難免,當下我就遇到這么個需求,希望在web端聊天室中可以發送表情,還得在web端、微信H5、app端、微信公眾號里均可以正常顯示出來
看到這個需求我的內心是這樣的
一番Google下來發現網上的大多都是移動端發送,以字典的方式匹配替換后web端只是單純的做顯示而已,難以找出符合我需求的文章了,那沒辦法,產品是老大,只能自己研究研究咯。。。
心癢癢的話就先看看效果:click me
完整demo在這兒:click me
設計方案一
最開始的設計思路是既然emoji表情有對應的Unicode碼,那么是不是可以直接就使用Unicode碼來進行傳輸及顯示,直接不做任何處理,基於各平台對Unicode的支持,讓其自生自滅呢?想着就干,嘗試一波再說。。。
看到這里如果真的動手去嘗試了么估計你是真的沒有好好了解一波emoji的Unicode碼,只要進入官網或者各統計平台應該應該都可以看到這個東西
由上圖可以看出個移動端對emoji的支持都存在如此大的差異,那么PC端的差距不看也知道不忍直視了,當然可能基於好奇和專研的精神,個別還是想去嘗試一波,斷絕念頭。。。好比如我
<!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>
你可以將如上代碼運行於各瀏覽器,你可能看到這個效果
- chrome
- Firefox
- IE
- Edge
- Android
由上可以看出各端的差距主要在移動端和IE之間,特別是移動端和PC端的差距,幾乎改頭換面了,這不說產品,自己這關很明顯都過不了,況且IE非edge模式下還是黑白色。所以這個設計方案很明顯被pass掉了
設計方案二
方案一的失敗之處很明顯,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存儲字段
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轉換及發送消息
// 將輸入框中的圖片替換為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到圖片轉換
// 將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的方式插入到顯示器