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

效果图

文件图

直接贴代码

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="initEmotionImg"
      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 * rowCount + idx"
          @click="selectEmotion(itm)"
        >
          <img :src="transEmotionToImg(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, //表情弹窗
      initEmotionImg: false, //表情图片初始化
      rowCount: emotionConfigList[0].length, //判断一行最大表情数
    });

    // 将表情文字替换为对应的图片
    function transEmotionToImg(info) {
      //根据位置匹配当前表情对应的图片地址(图片地址由微信QQ开源,后期可按照固定格式自定义)
      return `https://res.wx.qq.com/mpres/htmledition/images/icon/emotion/${emotionList.indexOf(info)}.gif`;
    }

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

    // 切换表情区域显隐
    function toggleEmotionShow() {
      state.emotionShow = !state.emotionShow;
      if (!state.emotionShow) {
        props.elem.focus(); //用户取消表情选择,输入框重新获焦
      }
      if (!state.initEmotionImg) {
        state.initEmotionImg = true; //首次弹窗时,再进行表情的加载,而不是一进入页面就加载表情
      }
    }

    return {
      emotionConfigList,
      selectEmotion,
      toggleEmotionShow,
      transEmotionToImg,
      ...toRefs(state),
    };
  },
};
</script>
<style scoped lang="less">
.emotion-container {
  position: relative;
  z-index: 1400;
  .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: grid;
      grid-template-columns: repeat(8, 1fr);
      .every-emotion-item {
        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}`;
  }
  elem.focus(); //用户选择表情后,输入框重新获焦
  return resValue;
}

export { transEmotion, insertEmotion };


emotionData.js

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

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

如何调用

<div v-html="transEmotion(commentValue)"></div>  //transEmotion方法用于渲染带有表情标识的文本

<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>
posted @ 2023-12-28 16:52  huihuihero  阅读(767)  评论(0编辑  收藏  举报