vue實現聊天+圖片表情功能

久宇詩發表於2021-11-24

專案需求是這樣的:要求實現類似於微信聊天一樣,表情+文字效果 “文字效果???”

表情包三種方案

表情包的實現其實可以分為以下三種情況:

  1. 表情包:點選表情--直接傳送大表情(這種方案其實就是傳送了一張定義好的圖片而已)
  2. emoji圖示表情:系統可識別的emoji圖示表情,這種表情實現起來相對麻煩一些,其實這種方法emoji我們可以當成一個2位字元的特殊文字去處理
    • 推薦emoji網址:emojis
      • 案例:禾蛙招聘網站
  3. 自定義表情:一版是設計畫好的,需要我們實現文字+表情進行傳送和回顯的
    • 思路1:我們在點選單個表情的時候輸入框內需要新增表情,這是輸入框內表情可以用‘[微笑]‘這種方式代替。只有在回顯的時候去識別特殊表示,再用表情去替換掉即可
      • 案例:BOSS直聘網站
    • 思路2:輸入框也需要是表情的回顯,這種方法就相對麻煩了一些
      • 案例:微信客戶端

實現

這裡針對自定義表情包的思路2,進行實現,個人認為也是最麻煩的實現
實現效果
這裡只針對輸入框進行實現,對於資料傳遞給後端,訊息的回顯這裡就不進行闡述了,想必大家也有自己更好的設計方法。

1、佈局

大家可能會想到輸入區域可以用用input,或者是textarea標籤,但針對‘文字+自定義圖片表情’是不太適用的,不過對於其他的實現方案還是可行的。
實現思路

  • 採用div+ contenteditable="true"屬性
    • 舉例:'

      這是一個可編輯段落。

      '

contenteditable屬性:

  • 屬性指定元素內容是否可編輯。
  • 所有主流瀏覽器都支援 contenteditable 屬性
  • 設定了true:該標籤預設就存在了@fcous,@input等事件函式
  • 注意: 當元素中沒有設定 contenteditable 屬性時,元素將從父元素繼承。
描述
true 指定元素是可編輯的
false 指定元素是不可編輯的
    <div
      id="emojiText"
      contenteditable="true"
      ref="edit"
      placeholder="請輸入內容"
    ></div>

2、實現

這裡實現思路包括三個方向:

  1. 新增表情,新增文字
  2. 獲取輸入的訊息內容,傳遞給後端
  3. 拿到特殊訊息體'[微笑]',渲染對應的表情

新增表情,新增文字
輸入文字:單純的輸入文字其實很簡單,思路就是可編輯區域可輸入對應的文字內容
新增表情:實現思路是,再點選新增表情時就是講表情地址生成<img>標籤插入到可編輯區域內
新增表情存在的問題:
問題1:新增表情時游標不會聚焦到可編輯區域
問題2:新增表情無法新增到游標定位的位置上
問題1解決方案
新增表情時檢查是否聚焦,無聚焦執行fcous強制聚焦

let obj = this.$refs.edit; // obj 是可編輯的div
    let range, node;
    if (!obj.hasfocus) {
    obj.focus();
}

問題2解決方案
window.getSelection:選擇的文字範圍或游標的當前位置。
getRangeAt
返回選區開始的節點(Node)。
因為通常情況下使用者只能選擇一個範圍,所以只有一個選區(range),此方法一般為getRangeAt(0)

range = window.getSelection().getRangeAt(0);
range.collapse(false); //游標移至最後

node = range.createContextualFragment(Img);
let c = node.lastChild;
range.insertNode(node);
if (c) {
    range.setEndAfter(c);
    range.setStartAfter(c);
}
let j = window.getSelection();
j.removeAllRanges();
j.addRange(range);

完整程式碼

<template>
  <div>
    <div
      id="emojiText"
      contenteditable="true"
      ref="edit"
      placeholder="請輸入內容"
    ></div>
    <ul>
      <li v-for="item in emojis" :key="item.name">
        <img :src="item.path" alt="" @click="getEmojis" />
      </li>
    </ul>
    <button @click="senMsg">傳送</button>
  </div>
</template>

<script>
export default {
  components: {},
  data() {
    return {
      emojis: [
        {
          name: "[emoji-1]",
          path: require("./assets/emojis/m1.png"),
        },
        {
          name: "[微笑]",
          path: require("./assets/emojis/m2.png"),
        },
        {
          name: "[emoji-3]",
          path: require("./assets/emojis/m3.png"),
        },
      ],
    };
  },
  mounted() {},
  methods: {
    getEmojis(event) {
      let Img = `<img src="${event.target.src}" style='height:10px;'>`;

      let obj = this.$refs.edit; // obj 是可編輯的div
      let range, node;
      if (!obj.hasfocus) {
        obj.focus();
      }
      /* 
        window.getSelection:選擇的文字範圍或游標的當前位置。
        getRangeAt
            返回選區開始的節點(Node)。
            因為通常情況下使用者只能選擇一個範圍,所以只有一個選區(range),此方法一般為getRangeAt(0)
      */
      if (window.getSelection && window.getSelection().getRangeAt) {
        range = window.getSelection().getRangeAt(0);
        range.collapse(false); //游標移至最後

        node = range.createContextualFragment(Img);
        let c = node.lastChild;
        range.insertNode(node);
        if (c) {
          range.setEndAfter(c);
          range.setStartAfter(c);
        }
        let j = window.getSelection();
        j.removeAllRanges();
        j.addRange(range);
      } else if (document.selection && document.selection.createRange) {
        document.selection.createRange().pasteHTML(Img);
      }
    },
    senMsg() {
      console.log(this.$refs.edit.innerHTML);
    },
    natchMsg() {
      let str = "比如‘[微笑]’asdtgsaghd[微笑]ggg";
      let newStr = str;
      this.emojis.forEach((item) => {
        console.log(str.indexOf(item.name));
        if (str.indexOf(item.name) > -1) {
          let Img = `<img src="${item.path}" style='height:10px;'>`;
          newStr = str.replaceAll(item.name, Img);
        }
      });
      console.log(newStr);
    },
  },
};
</script>

<style lang='scss' scoped>
#emojiText {
  width: 600px;
  height: 300px;
  border: 1px solid #111;
}
ul {
  display: flex;
  list-style: none;
}
li img {
  width: 20px;
  height: 20px;
}
.msgicon {
  width: 10px;
  height: 10px;
}
</style>
``

相關文章