Emoji-Chat emoji表情包發送及顯示兼容web端、移動端

序言

在現在聊天表情包滿天飛的當下,聊天過程當中想發送個表情感慨一下情緒在所不免,當下我就遇到這麼個需求,但願在web端聊天室中能夠發送表情,還得在web端、微信H五、app端、微信公衆號裏都可以正常顯示出來css

看到這個需求個人心裏是這樣的html

崩潰

一番Google下來發現網上的大多都是移動端發送,以字典的方式匹配替換後web端只是單純的作顯示而已,難以找出符合我需求的文章了,那沒辦法,產品是老大,只能本身研究研究咯。。。node

心癢癢的話就先看看效果:click megit

完整demo在這兒:click megithub


設計方案一

最開始的設計思路是既然emoji表情有對應的Unicode碼,那麼是否是能夠直接就使用Unicode碼來進行傳輸及顯示,直接不作任何處理,基於各平臺對Unicode的支持,讓其自生自滅呢?想着就幹,嘗試一波再說。。。web

看到這裏若是真的動手去嘗試了麼估計你是真的沒有好好了解一波emoji的Unicode碼,只要進入官網或者各統計平臺應該應該均可以看到這個東西算法

emojipic

由上圖能夠看出個移動端對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>

你能夠將如上代碼運行於各瀏覽器,你可能看到這個效果數據庫

  1. chrome
    emoji1
  2. Firefox
    emoji2
  3. ​ IE
    emoji2
  4. ​ Edge
    emoji4
  5. Android
    emoji5

由上能夠看出各端的差距主要在移動端和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">&#xe612;</span>
            <span v-if="eInd == 1"  slot="label" class="iconfont emoji_pane_tab">&#xe609;</span>
            <span v-if="eInd == 2"  slot="label" class="iconfont emoji_pane_tab">&#xe62d;</span>
            <span v-if="eInd == 3"  slot="label" class="iconfont emoji_pane_tab">&#xe63c;</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">&#xe612;
        </el-button>
      </el-popover>
    </div>

    <!-- 顯示內容區 -->
    <div>發送的消息:</div>
    <div class="chatframe-text text_emoji" v-html="changeEmojiCon(sendValue)"></div>
  </div>
</template>

頁面代碼設計相對簡單,能夠根據本身需求選取,重點是接下來的JS方法解讀(這裏只列出個別重要一點的方法作解釋,完整demo自尋倉庫取)

  1. 定義data存儲字段
    data () {
      return {
        emojiIcon: emojiData.icon,    // 導入的emoji表情配置文件內容
        emojiPath: new Map(),         // emoji表情地址map對象,
        inputRange: '',               // 光標
        sendValue: '',                // 發出的內容
      }
    }

    這其中涉及到一個字段爲inputRange(光標),這裏解釋一下,由於插入emoji表情時須要動態的往用戶編輯的當前光標處去插入emoji,因此無論用戶是輸入仍是點擊都須要獲取一下當前光標,而後將其記錄下來,當點擊emoji表情是就將其插入到記錄的光標處

  2. 記錄光標
    // 延時記錄光標到位置
    saveRangeLocal () {
      setTimeout(() => {
        this.inputRange = window.getSelection().getRangeAt(0)
      }, 0)
    }

    獲取光標保存時並不能直接就實時獲取,不然會因此報錯或undefined,因此這裏使用宏任務來進行延時獲取

  3. 插入表情到富文本
    // 點擊表情,將表情添加到輸入框
    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屬性,用於解析時字典對比替換

  4. 圖片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‘

  5. 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的方式插入到顯示器

相關文章
相關標籤/搜索