首页 > 其他分享 >封装一个表情包组件(支持自定义表情图片)(基于vue3语法)

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

时间:2023-12-28 16:57:19浏览次数:28  
标签:emotion 自定义 elem value state let 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="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>

标签:emotion,自定义,elem,value,state,let,vue3,表情图片,表情
From: https://www.cnblogs.com/huihuihero/p/17933063.html

相关文章

  • Spring 框架如何创建和解析自定义的 `<mvc:annotation-driven/>` 标签
    跟着孙哥学Spring,b站:https://www.bilibili.com/video/BV185411477k/?spm_id_from=333.337.search-card.all.clickSpring框架如何创建和解析自定义的<mvc:annotation-driven/>标签。1.创建BeanDefinitionParser首先,我们需要创建一个BeanDefinitionParser实现类来解析自......
  • [日志] lo4j2之自定义日志格式变量
    1PatternLayout/LogEventPatternConverter:自定义日志格式及格式变量在Log4j或Logback等Java日志框架中,PatternLayout类允许你定义日志输出的格式。PatternLayout通过一系列的转换器(PatternConverter)来定义输出的样式。其中,LogEventPatternConverter(日志格式化......
  • Postgresql中PL/pgSQL的游标、自定义函数、存储过程的使用
    场景Postgresql中PL/pgSQL代码块的语法与使用-声明与赋值、IF语句、CASE语句、循环语句:Postgresql中PL/pgSQL代码块的语法与使用-声明与赋值、IF语句、CASE语句、循环语句上面讲了基本语法,下面记录游标、自定义函数、存储过程的使用。注:博客:霸道流氓气质_C#,架构之路,SpringBoot实......
  • 使用命令行创建vue3+Typescript的uni-app
    目录创建项目扩展组件uni-ui安装配置easycom安装pinia报vue.hasInjectionContextisnotafunction更具该文档创建好的模板:GitHub仓库地址,克隆下来安装依赖即可创建项目官方文档--创建uni-app以创建vue3+Typescript工程为例,使用下列命令行:#网络不好的话会创建失败,可以前往......
  • 哪里有流程自定义表单?
    如果需要提升办公协作效率,可以借助流程自定义表单的功能和价值,快速进入办公流程化发展阶段。那么,哪里有流程自定义表单可以体验?流程自定义表单又有哪些优势特点?通过这篇文章,我们一起来了解流程自定义表单和低代码技术平台的相关特点。我们都知道,随着社会的进步和发展,很多企业已经......
  • Spring Boot学习随笔- 后端实现全局异常处理(HandlerExceptionResolver),前后端解决跨域
    学习视频:【编程不良人】2021年SpringBoot最新最全教程第十七章、异常处理异常处理作用:用来解决整合系统中任意一个控制器抛出异常时的统一处理入口传统方式传统单体架构下的处理方式配置全局异常处理类@ComponentpublicclassGlobalExceptionResolverimplementsHand......
  • vue2 自定义插件
    自定义插件的基本使用:letMyPlugin={};MyPlugin.install=function(Vue,options){//1.添加全局方法或propertyVue.myGlobalMethod=function(){//逻辑...}//2.添加全局资源Vue.directive('my-directive',{bind(el,binding,vnode,......
  • vite+vue3 打包后页面空白现象
    使用vite打包之后运行index.html空白,打开控制台发现报错:解决方法:在vite.config中加入:publicPath:'./',这是vite.config中的结构: exportdefaultdefineConfig({publicPath:'./',lintOnSave:false,transpileDependencies:true,plugins:[......
  • vue3横向时间轴展示
    架子是用的vue3+elementPlus,要用到时间轴展示,但element组件只有竖着的,想要横着的,找了一圈没有合适的,终于找到个合适的,文章原址https://blog.csdn.net/m0_62949703/article/details/127800712数据结构: {date:'2023-09-27’,isShow:true,children:[......
  • Go简单自定义协程池
    packagemainimport( "fmt" "sync")typeTaskstruct{ ffunc()error}varwgsync.WaitGrouptypePoolstruct{ //任务通道 JobQueuechanTask //worker通道 WorkerQueuechanchanTask //worker数量 MaxWorkersint}funcNewPool(ma......