效果图
文件图
直接贴代码
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>