了解前端emoji

1,249 阅读6分钟

需要先了解下: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 => &#128512;</p>  // 0x1F602 => 😂
<p> 0x597d=> &#22909;</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~