学习背景
在日常使用的社交软件如秋秋和微信的时候,我们常能用到他们的表情功能,非常的方便快捷。一套好的表情包能给用户带来很好的体验,就比如下面这张QQ的表情截图。最近有一个给小程序加这种小表情的需求,在尝试和学习了各式各样的方法后,在这里做一个总结。
实现过程
- 表情的存储与获取
- 选取框的制作
- 渲染方式
1. 表情的存储与获取
前提: 本文的所说的表情是以 .jpg .png 等常用格式为主的图片。
这个部分还是比较简单的,关于图片的存储,要么是放在本地,要么放在网络。虽然普通小黄脸类表情2x@png格式下最多也就1kb左右, 但如果以后想要增加不同的表情合集的话也有几十甚至几百kb了,对小程序来说还是有些吃不消的。
那要怎么让网络图片展示到选取页面呢?这时候我们很容易就能想到用标识符来代替表情,例如 [haha] 在选取面板上的时候展示为 😄,我们可以通过一个函数来生成对应的网络地址或者建立一个映射表将标识符映射到对应的图片网络地址上。
函数形式实现参考:
/**
* @param {string[]} baseUrl 网络地址的头部
* @param {string[]} sign 图片的标识符
* @param {string[]} ext 图片的格式
* @return {string[]}
*/
const imgAddMap = (baseUrl, sign, ext) => {
//这里可以对sign.slice结果进一步自定义处理得到需要的路径
return baseUrl + sign.slice(1, -1) + ext;
};
console.log(
imgAddMap('http://localhost:8080/abc/', '[haha]', '.png')
);
// http://localhost:8080/abc/haha.png
表实现形式参考:
// 实现一个 img util
const baseUrl = 'http://localhost:8080/abc';
const imgName = ['[haha]', '[wuwu]' /*more ...*/];
const imgMap = {
'[haha]': 'lalala.png',
'[wuwu]': 'yingyingying.png',
// more ...
};
/**
* @param {string[]} imgSign 图片的标识符
* @return {string[]}
*/
const imgDisplay = (imgSign) => {
const _img = imgMap[imgSign];
return _img ? baseUrl + _img : '';
};
console.log(imgDisplay('[haha]'));
// http://localhost:8080/abclalala.png
// export { imgName, imgMap, imgDisplay }; 最后将模块导出
简要对比:
- 函数实现无需再声明其他相关变量或对象来存储,但是每次都需要经过计算。
- 表实现查找效率更高,但是需要额外的对象来存储地址和格式等。
2. 选取框的制作
在使用表情包之前我们得有个展示的页面,布局大家很容易就能想到只需要划好正方形格子然后把表情图片塞进去就好了。
//个人习惯实现方式如下
#outer {
position: relative;
width: 12.5%;
height: 0;
padding-top: 12.5%;
}
// aspect-ratio: 1/1; 在h5中使用正常,在微信小程序中貌似有兼容问题
.inner {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
}
表情选择展示部分(此处为了展示效果使表情重复了几次,其实就是懒)
<div id="outer">
<div class="inner" v-for="item in imgName">
<span class="img">
<img
:src="_imgDisplay(item)"
alt=""
style="width: 60%; height: 60%"
/>
</span>
</div>
</div>
示例图片: (12.5% / 图片)
这里我们添加一个输入框再给表情添加指定的点击事件使表情的标识符加入到输入框中以用于后期的渲染。
此处是直接采用 input值 = 当前值 + 表情标识符 进行简单相加。没有实现 插入 操作。H5, 各类小程序实现的方法有所不同,大家可以自己试试在光标处插入。
到这里我们的目的就已经完成了一半了,接下来就是主要的渲染部分了。
3. 渲染方式
获取到了消息接下来我们就得考虑如何将其渲染到消息中去了,此处提供两种参考方式:
- 使用正则表达式替换括号中的内容为图片再通过 innerHTML 最终展示出来
- 封装一个函数将字符串生成虚拟dom节点
a.正则表达式替换
/**
* @param {string[]} msg 需要替换的内容
* @return {string[]}
*/
const imgTextReplacer = (msg) => {
return msg.replace(/\[([a-z0-9]+?)\]/g, (name) => {
// 正则表达式这里根据自己的需求替换 ~
if (imgDisplay(name)) {
return `<img src="${imgDisplay(name)}"
class="imgText"></img>`;// imgText为最终图片的样式
} else {
return name;
}
});
};
在html中进行展示:
<div v-html="_imgTextReplace(inputContent)" class="_display"></div>
图片样式可以和前面说的一样添加 imgText 样式,也可以在替换的时候直接写style,但是不要忘记了图片和文字的对齐,可以在父元素 ._display
上添加 flex 盒模型的垂直居中 align-items:baseline
,也可以给图片 .imgText
加上样式 vertical-align: text-bottom
。
需要注意的是如果给图片加样式的话可能在vue上的样式不生效,可以尝试在 钩子updated 中给元素添加相应的二次处理或者去除 style 中的 scoped 属性
b.render函数
/**
* @param {string[]} msg 要渲染的消息
* @return {string[]}
*/
const imgTextParser = (msg) => {
const res = [];
let left = -1;
let right = -1;
while (msg.length) {
left = msg.indexOf('[');
right = msg.indexOf(']');
if (left === 0) {
if (right === -1) {
res.push({ tag: 'text', text: msg });
break;
}
// 在表中查找 img 是否存在
const _img = imgMap[msg.slice(0, right + 1)];
if (_img) {
res.push({
tag: 'image',
src: baseUrl + _img,
});
msg = msg.slice(right + 1);
} else {
res.push({
tag: 'text',
text: '[',
});
msg = msg.slice(1);
}
} else {
// 是否找到了 left
const ifFindLeft = left !== -1;
res.push({
tag: 'text',
text: ifFindLeft ? msg.slice(0, left) : msg,
});
msg = ifFindLeft ? msg.slice(left) : '';
}
}
return res;
};
在页面结构中的代码 (H5):
// 某一条消息
<div class="_display">
<div class="" v-for="_d in msg">
<span v-if="_d.tag === 'text'"> {{ _d.text }} </span>
<img :src="_d.src" alt="bq" style=""
class="_img" v-if="msg.tag === 'image'"
/>
</div>
</div>
最终展示效果
有时候居中会更好~
无论最终选择哪种方法,都要注意渲染的时机,不要像上面那样在html上调用函数,高频率的 DOM 操作会使页面卡顿严重。应该提前就把该替换的给替换好。
总结
到此我们就实现了一个简易的表情选择器了,大家可以根据自己的喜好或需求对功能进行加强,不足或错误之处欢迎大家指出🤗🤗🤗~
【MINI 系列】