封装一个表情包组件(支持自定义表情图片)(基于vue3语法)

发布时间 2023-12-28 16:55:45作者: huihuihero

效果图

文件图

直接贴代码

emotion.vue

<template>
  <div class="emotion-container beauty-scroll-livechat">
    <div class="emotion-btn" @click="toggleEmotionShow">
      <span class="iconfont icon-biaoqing1"></span>
    </div>
    <div
      class="emotion-box"
      v-if="emotionLoaded"
      v-show="emotionShow"
      :style="{ width: `${width}px`, height: `${height}px` }"
    >
      <div class="every-emotion-line" v-for="(line, index) in emotionConfigList" :key="index">
        <div
          class="every-emotion-item"
          v-for="(itm, idx) in line"
          :key="index * line.length + idx"
          :ref="(defaultData) => getEmotionElemGather(defaultData, index * line.length + idx)"
          @click="selectEmotion(itm)"
        >
          {{ itm }}
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { nextTick, reactive, toRefs } from 'vue';
import { emotionList, emotionConfigList } from './emotionData';
import { insertEmotion } from './emotionMethod';
export default {
  name: 'Emotion',
  props: {
    //当前操作的输入框的绑定值(必填)
    value: {
      type: [Number, String],
      required: true,
      default: '',
    },
    // 当前操作的输入框元素实例
    elem: {
      required: true,
      default: null,
    },
    // 表情区域宽度
    width: {
      type: Number,
      default: 450,
    },
    // 表情区域高度
    height: {
      type: Number,
      default: 200,
    },
  },
  setup(props, { emit }) {
    const state = reactive({
      emotionShow: false, //表情是否展示
      emotionLoaded: false, //表情是否加载
      emotionElemGather: {}, //所有表情实例的集合
    });

    // 处理表情实例
    function getEmotionElemGather(elem, index) {
      if (elem) {
        state.emotionElemGather[`elem${index}`] = elem;
      }
    }

    // 将表情文字替换为对应的图片
    function transEmotionToImg() {
      for (let item of Object.keys(state.emotionElemGather)) {
        let everyElem = state.emotionElemGather[item]; //获取元素实例
        let emotionName = everyElem.innerHTML; //获取表情名
        let index = emotionList.indexOf(emotionName); //匹配当前表情名在表情列表中的位置
        //根据位置匹配当前表情对应的图片地址(图片地址由微信QQ开源,后期可按照固定格式自定义)
        let imgHTML = `<img src="https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/${index}.gif">`;
        nextTick(() => {
          everyElem.innerHTML = imgHTML; //将dom中的表情名替换为图片
        });
      }
    }

    // 选择某个表情时触发
    function selectEmotion(name) {
      let res = insertEmotion(props.elem, props.value, `#${name};`);
      emit('update:value', res);
    }

    // 切换表情区域显隐
    function toggleEmotionShow() {
      state.emotionShow = !state.emotionShow;
      if (!state.emotionLoaded) {
        state.emotionLoaded = true;
        nextTick(() => {
          transEmotionToImg();
        });
      }
    }

    return {
      emotionConfigList,
      getEmotionElemGather,
      selectEmotion,
      toggleEmotionShow,
      ...toRefs(state),
    };
  },
};
</script>
<style scoped lang="less">
.emotion-container {
  position: relative;
  z-index: 6000;
  .emotion-btn {
    display: inline-block;
    color: #333;
    user-select: none;
    cursor: pointer;
    transition: all 0.2s;
    .iconfont {
      font-size: 26px;
      line-height: normal;
    }
    &:hover {
      color: @tc-main;
    }
  }
  .emotion-box {
    position: absolute;
    top: 40px;
    left: 0;
    padding: 10px 10px;
    box-sizing: border-box;
    border-radius: 8px;
    background-color: rgba(255, 255, 255, 0.95);
    box-shadow: 0 0 4px rgba(@tc-main-rgb, 0.4);
    overflow-x: hidden;
    overflow-y: auto;
    user-select: none;
    .every-emotion-line {
      display: flex;
      .every-emotion-item {
        flex: 1;
        padding: 6px 0;
        text-align: center;
        line-height: 0;
        cursor: pointer;
      }
    }
  }
}
</style>

emotionMethod.js

import { nextTick } from 'vue';
import { emotionList, emotionConfigList } from './emotionData';

// 匹配表情名并替换为图片(图片后期可按照固定格式自定义)
function matchNameToImg(value) {
  let word = value.replace(/\#|\;/gi, '');
  let index = emotionList.indexOf(word);
  return `<img src="https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/${index}.gif" style="vertical-align:bottom">`;
}

/**
 * 处理带表情的文本内容,将文本中的表情替换为实际图片展示
 * 【使用场景:dom层中表情文本的渲染时使用】 如 <div v-html="transEmotion(commentValue)"></div>
 * @param { String } value  文本内容
 */
function transEmotion(value) {
  return value.replace(/\#[\u4E00-\u9FA5]{1,3}\;/gi, matchNameToImg);
}

// 获取输入框的光标位置
function getCursorPosition(elem) {
  let caretPos = 0;
  if (document.selection) {
    // 兼容IE浏览器
    elem.focus();
    let sel = document.selection.createRange();
    sel.moveStart('character', -elem.value.length);
    caretPos = sel.text.length;
  } else if (elem.selectionStart || elem.selectionStart === 0) {
    // 兼容其他浏览器
    caretPos = elem.selectionStart;
  }
  return caretPos;
}
// 设置输入框的光标位置
function setCursorPosition(elem, posi) {
  if (elem.setSelectionRange) {
    elem.focus();
    elem.setSelectionRange(posi, posi);
  } else if (elem.createTextRange) {
    var range = elem.createTextRange();
    range.collapse(true);
    range.moveEnd('character', posi);
    range.moveStart('character', posi);
    range.select();
  }
}
/**
 * 向文本中当前光标所在处插入表情
 * 【使用场景:向输入框内插入表情时使用】
 * @param elem  输入框元素实例
 * @param { String } value  输入框内当前的文本内容
 * @param { String } emotion  表情名
 */
function insertEmotion(elem, value, emotion) {
  let posi = getCursorPosition(elem); //获取当前光标位置
  let resValue = '';
  if (posi >= 0) {
    let startSection = value.slice(0, posi);
    let endSection = value.slice(posi);
    resValue = `${startSection}${emotion}${endSection}`;
    nextTick(() => {
      let timer = setTimeout(() => {
        clearTimeout(timer);
        setCursorPosition(elem, posi + emotion.length); //添加表情后的光标位置需要后移几位
      }, 10);
    });
  } else {
    resValue = `${value}${emotion}`;
  }
  return resValue;
}

export { transEmotion, insertEmotion };

emotionData.js

//所有表情列表
export const emotionList = [
  '微笑',
  '撇嘴',
  '色',
  '发呆',
  '得意',
  '流泪',
  '害羞',
  '闭嘴',
  '睡',
  '大哭',
  '尴尬',
  '发怒',
  '调皮',
  '呲牙',
  '惊讶',
  '难过',
  '酷',
  '冷汗',
  '抓狂',
  '吐',
  '偷笑',
  '可爱',
  '白眼',
  '傲慢',
  '饥饿',
  '困',
  '惊恐',
  '流汗',
  '憨笑',
  '大兵',
  '奋斗',
  '咒骂',
  '疑问',
  '嘘',
  '晕',
  '折磨',
  '衰',
  '骷髅',
  '敲打',
  '再见',
  '擦汗',
  '抠鼻',
  '鼓掌',
  '糗大了',
  '坏笑',
  '左哼哼',
  '右哼哼',
  '哈欠',
  '鄙视',
  '委屈',
  '快哭了',
  '阴险',
  '亲亲',
  '吓',
  '可怜',
  '菜刀',
  '西瓜',
  '啤酒',
  '篮球',
  '乒乓',
  '咖啡',
  '饭',
  '猪头',
  '玫瑰',
  '凋谢',
  '示爱',
  '爱心',
  '心碎',
  '蛋糕',
  '闪电',
  '炸弹',
  '刀',
  '足球',
  '瓢虫',
  '便便',
  '月亮',
  '太阳',
  '礼物',
  '拥抱',
  '强',
  '弱',
  '握手',
  '胜利',
  '抱拳',
  '勾引',
  '拳头',
  '差劲',
  '爱你',
  'NO',
  'OK',
  '爱情',
  '飞吻',
  '跳跳',
  '发抖',
  '怄火',
  '转圈',
  '磕头',
  '回头',
  '跳绳',
  '挥手',
  '激动',
  '街舞',
  '献吻',
  '左太极',
  '右太极',
];

//表情行列配置列表
export const emotionConfigList = [
  ['微笑', '撇嘴', '色', '发呆', '得意', '流泪', '害羞', '闭嘴'],
  ['睡', '大哭', '尴尬', '发怒', '调皮', '呲牙', '惊讶', '难过'],
  ['酷', '冷汗', '抓狂', '吐', '偷笑', '可爱', '白眼', '傲慢'],
  ['饥饿', '困', '惊恐', '流汗', '憨笑', '大兵', '奋斗', '咒骂'],
  ['疑问', '嘘', '晕', '折磨', '衰', '骷髅', '敲打', '再见'],
  ['擦汗', '抠鼻', '鼓掌', '糗大了', '坏笑', '左哼哼', '右哼哼', '哈欠'],
  ['鄙视', '委屈', '快哭了', '阴险', '亲亲', '吓', '可怜', '菜刀'],
  ['西瓜', '啤酒', '篮球', '乒乓', '咖啡', '饭', '猪头', '玫瑰'],
  ['凋谢', '示爱', '爱心', '心碎', '蛋糕', '闪电', '炸弹', '刀'],
  ['足球', '瓢虫', '便便', '月亮', '太阳', '礼物', '拥抱', '强'],
  ['弱', '握手', '胜利', '抱拳', '勾引', '拳头', '差劲', '爱你'],
  ['NO', 'OK', '爱情', '飞吻', '跳跳', '发抖', '怄火', '转圈'],
  ['磕头', '回头', '跳绳', '挥手', '激动', '街舞', '左太极', '右太极'],
];


如何调用

<div v-html="transEmotion(commentValue)"></div>  //transEmotion方法用于渲染,以v-html方式

<div class="comment-add">
  <textarea ref="textareaElem" v-model="commentValue" />
  <emotion v-model:value="commentValue" :elem="textareaElem"></emotion>  //组件调用
</div>

<script>
import { transEmotion } from '@/components/regional/emotion/emotionMethod';
import Emotion from '@/components/regional/emotion/emotion';
export default {
  components: {
    Emotion,
  },
  setup() {
    const state = reactive({
      commentValue: '', //评论内容
      textareaElem: null, //输入框实例
    });

    return {
      transEmotion,
      ...toRefs(state),
    }
  }
}
</script>