vue realizes the function of chat + picture expression

The project requirements are as follows: it is required to realize the "expression + text effect" similar to wechat chat 😀😀😀”

Three schemes of expression package

The implementation of expression package can be divided into the following three cases:

  1. Expression package: click expression - directly send a big expression (this scheme actually sends a defined picture)
  2. emoji icon expression: emoji icon expression that can be recognized by the system. This expression is relatively troublesome to implement. In fact, this method can be treated as a special 2-bit character
    • Recommended emoji website: emojis
      • Case: hefrog recruitment website
  3. Custom expression: the first version is designed and drawn. We need to send and echo text + expression
    • Idea 1: when we click a single expression, we need to add an expression in the input box. This is the expression in the input box, which can be replaced by '[smile]'. Only when it is echoed, can we recognize the special expression and replace it with an expression
      • Case: BOSS direct employment website
    • Idea 2: the input box also needs to echo the expression. This method is relatively troublesome
      • Case: wechat client

realization

Here, for the idea 2 of custom expression package, I think it is also the most troublesome implementation
Realization effect
Only the input box is implemented here. The echo of messages when data is transmitted to the back end is not described here. Presumably, everyone also has their own better design methods.

1. Layout

You may think that the input area can use input or textarea tags, but it is not suitable for 'text + custom image expression', but it is still feasible for other implementation schemes.
Realization idea

  • Adopt div + contentitable = "true" attribute
    • Example: '

      This is an editable paragraph.

      '

Contentditable attribute:

  • Property specifies whether the element content is editable.
  • All mainstream browsers support the contentable attribute
  • Set true: the tag has @ fcous, @ input and other event functions by default
  • Note: when the contenteditable attribute is not set in the element, the element will inherit from the parent element.
value describe
true Specifies that the element is editable
false The specified element is not editable
    <div
      id="emojiText"
      contenteditable="true"
      ref="edit"
      placeholder="Please enter the content"
    ></div>

2. Realize

The implementation idea here includes three directions:

  1. Add expression, add text
  2. Get the input message content and pass it to the back end
  3. Get the special message body '[smile]' and render the corresponding expression

Add expression, add text
Input text: simple input text is actually very simple. The idea is that the corresponding text content can be input in the editable area
Add expression: the implementation idea is that when clicking Add expression again, the expression address generation < img > label is inserted into the editable area
Problems with adding emoticons:
Question 1: when adding an expression, the cursor will not focus on the editable area
Problem 2: adding an expression cannot be added to the position where the cursor is positioned
Problem 1 solution
When adding an expression, check whether it is focused. If there is no focus, perform fcous forced focus

let obj = this.$refs.edit; // obj is an editable div
    let range, node;
    if (!obj.hasfocus) {
    obj.focus();
}

Problem 2 solution
window.getSelection: the selected text range or the current position of the cursor.
getRangeAt
Returns the Node at the beginning of the selection.
Generally, users can only select one range, so there is only one range. This method is generally getRangeAt(0)

range = window.getSelection().getRangeAt(0);
range.collapse(false); //Move cursor to last

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);

Complete code

<template>
  <div>
    <div
      id="emojiText"
      contenteditable="true"
      ref="edit"
      placeholder="Please enter the content"
    ></div>
    <ul>
      <li v-for="item in emojis" :key="item.name">
        <img :src="item.path" alt="" @click="getEmojis" />
      </li>
    </ul>
    <button @click="senMsg">send out</button>
  </div>
</template>

<script>
export default {
  components: {},
  data() {
    return {
      emojis: [
        {
          name: "[emoji-1]",
          path: require("./assets/emojis/m1.png"),
        },
        {
          name: "[smile]",
          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 is an editable div
      let range, node;
      if (!obj.hasfocus) {
        obj.focus();
      }
      /* 
        window.getSelection: The selected text range or the current position of the cursor.
        getRangeAt
            Returns the Node at the beginning of the selection.
            Generally, users can only select one range, so there is only one range. This method is generally getRangeAt(0)
      */
      if (window.getSelection && window.getSelection().getRangeAt) {
        range = window.getSelection().getRangeAt(0);
        range.collapse(false); //Move cursor to last

        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 = "such as'[smile]'asdtgsaghd[smile]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>

Existing problem: the continuous expression in the input box is that you can't click the mouse to speak, the cursor can focus between the two expressions, and you can only focus with the left and right keys of the keyboard. (hope to provide a solution)

Posted by missy3434 on Wed, 24 Nov 2021 05:01:44 -0800