需要先了解下:Unicode码,也称万国码,是一种全球通用字符集,一个十六进制编码映射一个字符,我们现在看到的字符都是由采用的Unicode字符集,
具体参考万国码、Unicode字符大全
emoji是什么?
emoji是一个日本人创造的在无线通信中所使用的表情符号,用来表述用户情感的符号
emoji如何实现?
emoji实现角度来讲可以分为两种类型:
原生emoji、自定义emoji
虽然都是emoji,但是两者实现的方式却是不太一样的,下面会将两者优缺点、注意事项列举。
1.原生emoji
也就是我们通常说的emoji表情,我们生活中常用的Android、IOS、Window、Mac默认都是支持这种格式的emoji,它就跟我们现在看到的中文字符一样,不需要开发者额外进行开发,因为中文本身也是一个通过Unicode字符集映射出来的字符,emoji的本质也是一个十六进制的Unicode码,
例如:
中文“好”对应Unicode码为0x597d
😂对应Unicode码为0x1F602
所以综合以上信息,前端本身是支持emoji的,然而我们还会碰到一些问题:
1)字符长度问题:
由于JS语言的历史原因,JS只能处理UCS-2编码,意思是在JS中所有的字符都是2个字节,,而对于超出了2个字节的字符会被当作两个字节处理,放在辅助平面[\ud800-\udbff][\udc00-\udfff]
之间,用来弥补2个字节的不足,所以开发中要注意emoji的长度会被误判断为大于1。
参考阮一峰文章《Unicode与JavaScript详解》
'😂'.length // 2
'😂'.charCodeAt(0).toString(16) // 'd83d'
'😂'.charCodeAt(1).toString(16) // 'de02''😂' === '\ud83d\ude02' // true
'😂' === '\u{1F602}' // true, 由于ES6的拓展,加上{}后JS可以支持识别4字节的码点,所以也是成立的,
'🤏🏻'.length // 4
'🧑🎓'.length // 5 🧑🎓.....
2) 字符存储问题:
当我们开发的时候会碰到一个问题,把emoji字符串发送给服务器的时候,数据库就可能会返回一个错误 java.sql.SQLException: Incorrect string value: '\xF0\x9F\x92\x94' for column '***' at row 1
这里是由于服务器的数据库默认是按照utf-8编码规则去存储数据的,只能接收1-3个字节的字符,而一个emoji表情则可能存在3-4个字节的情况,数据库会出现报错 Incorrect string value
(字符不正确)
解决办法
1.服务端处理:数据库拓展存储更多字节的字符,前端不需要处理,完美~
2.前端处理:如果服务端只能存utf-8编码2个字节的字符,那前端就将4个字节的unicode码转换成2个字节的实体字符传递即可,
例如 0x1F602 是4个字节的字符,可以转换成八进制的实体字符 😂前端拿到实体字符也是可以直接渲染成对应字符的
<p> 0x1F602 => 😀</p> // 0x1F602 => 😂
<p> 0x597d=> 好</p> // 0x597d => 好
转码编码方法
//把utf16的emoji表情字符进行转码成八进制的字符function utf16toEntities(str) { // 检测utf16字符正则,只要落在0xD800到0xDBFF的区间,就要连同后面2个字节一起读取。
// 类似的问题存在于所有的JavaScript字符操作函数
var patt = /[\ud800-\udbff][\udc00-\udfff]/g;
return str.replace(patt, function (char) {
var H, L, code; if (char.length === 2) {
H = char.charCodeAt(0); // 取出高位
L = char.charCodeAt(1); // 取出低位
code = (H - 0xD800) * 0x400 + 0x10000 + L - 0xDC00; // 转换算法,知道这回事就行了~
return "&#" + code + ";"; // HTML实体符
} else {
return char;
}
});
}
//将编码后的八进制的emoji表情重新解码成十六进制的表情字符
function entitiesToUtf16(str) {
return str.replace(/&#(\d+);/g, function (match, dec) {
let H = Math.floor((dec - 0x10000) / 0x400) + 0xD800;
let L = Math.floor(dec - 0x10000) % 0x400 + 0xDC00;
return String.fromCharCode(H, L);
});
}
3) 各个平台的渲染差异:
原生emoji表情会有设备差异的问题,各个设备产商渲染画风会略有不同,但是表达的表情大致是一样的,这里需要跟产品提前确认好,是否需要各个设备表情保持一致,如果需要各设备保持一致的渲染效果,需要用自定义的emoji,微信,QQ等就是使用的自定义emoji
缺点:需要转码,各个平台有差异,不支持拓展,各个emoji长度不规范
2.自定义emoji
**适用场景:**由于原生emoji在各设备的渲染差异,如果对于各个设备的渲染效果有要求,那么自定义emoji就比较符合场景,目前许多社交APP都是用的这种方式
自定emoji通常是当前端收到类似 [微笑] 或者 /微笑 或者其他预设的字符串时,通过匹配规则,拿到对应的图片地址,然后将对应字符串替换成一个img标签即可, 需要在先配置一个emoji映射表以及对应的emoji表情图片
参考gitub 7.4K star的项目 **emoji-cheat-sheet
**
这个插件就是用枚举的方式,用 :key: 规则判断是否一个emoji标签,拿到key匹配对应的图片地址替换即可
// emojis.js
var emojis = { "money_mouth_face": "https://github.githubassets.com/images/icons/emoji/unicode/1f911.png?v8",
"money_with_wings": "https://github.githubassets.com/images/icons/emoji/unicode/1f4b8.png?v8",
"moldova": "https://github.githubassets.com/images/icons/emoji/unicode/1f1f2-1f1e9.png?v8",
"monaco": "https://github.githubassets.com/images/icons/emoji/unicode/1f1f2-1f1e8.png?v8",
....}
var replaceEmoji = function(str){
return str.replace(/:([a-zA-Z]+):/g,function($1){
return `<img src="${emojis[$1]}"/>`;
});
};
//app.js
var str = '测试:money_mouth_face:测试'; // 用户或者服务端返回带有emoji标识的字符串
str = replaceEmoji(str); // '测试<img src="***"/>测试'
优化:为了缓解前端请求并发的问题,可以将单个的emoji表情整合成一个大的雪碧图,通过枚举的方式匹配一个预设好的class,class提前配置好对应的坐标,这样前端只需要请求一次就能加载所有的emoji
// emoji.css
.emoji{ background:url('雪碧图url');}.emoji26c4 { background-position: -520px -200px; } // 需要提前配好坐标
.emoji26a1 { background-position: -520px -100px; }
.emoji1f300 { background-position: -20px -500px; }
.emoji1f301 { background-position: -20px -520px; }
.emoji1f302 { background-position: -20px -540px; }
.emoji1f303 { background-position: -20px -560px; }
...
// emojis.jsvar emojis = {
"money_mouth_face": "emoji26c4", // 这里映射成一个class类 "money_with_wings": "emoji26a1", "moldova": "emoji1f300 ", "monaco": "emoji1f301", ....
};var replaceEmoji = function(str){
return str.replace(/:([a-zA-Z]+):/g,function($1){
return `<i class="emoji ${emojis[$1]}"><i/>`;
});
};//app.js
var str = '测试:money_mouth_face:测试'; // 用户或者服务端返回带有emoji标识的字符串
str = replaceEmoji(str); // '测试<i class="emoji emoji26c4"><i/>测试'
总结
** 原生emoji**
优点:简单快捷,只需要数据库支持存储4字节字符或者前端进行转码即可,不需要太大开发量;
缺点: 1)各个设备对emoji的渲染风格不同,无法保证一致的渲染效果 。 2)无法满足自定义emoji的需求。
自定义emoji
优点:1) 各设备渲染效果完美且统一 2)可以拓展各式各样的emoji,甚至动图等 3) 可能会造成Http请求池阻塞
缺点:1)用户无法直接使用复制黏贴到其他的平台 2)工作量大,初始配置映射表的工作量大,后期需要维护emoji表
总而言之,根据业务场景选择自己合适的方式,目前我们的项目没有要求各设备渲染效果一致,且未要求后期拓展emoji,所以采用了原生的emoji~