By Yuanyue.韦
理论上所有聊天时能用的小图表情(绘文字)都算是 emoji,本文所说的 emoji 是指 Unicode 存在对应编码的系统内置的的小图表情。
在我们平时输入的文本中,emoji十分常见,但在显示和编码上它却是一个幺蛾子般的存在。
它诡异就诡异在:
emoji 这些诡异的特性给开发者们带来不少问题,比如:
对于第一个问题,升级 MySQL 并且用CHARACTER SET utf8mb4把字符集设置为UTF8mb4是最合适的处理方法,否则会出现在 emoji 处字符串被截断或存入数据库后全变成了“???”。
对于第二个问题,google 和 stackoverflow 上搜了一大圈都没找到和我有相同疑问的开发者,所以在群里讨论后想了一个方法,综合考虑了以下几个方面:
综上,现在给出一种解决方案,在之前这位穿女装成名现在在度蜜月没办法碰代码的前端工程师考虑的基础上改进,主要利用了在大部分系统下emoji不能被上色的原理,对于那些 emoji 可以被上色的平台做降级处理,在2*2的 canvas 上做像素比对。
这个方法对于用字体显示 emoji 并且对不识别的字符会显示方框或问号的平台不能准确区分,但这样的平台是相当罕见的,至少目前还没有见到过。
const getTextFeature = (text, color) => {
try {
const canvas = document.createElement('canvas');
因为进行scale以后的图案区域实际上不能确定,
理论上应该只在(0,0,1,1),但有的也会在它周围的像素里,
综合效率的考虑,给一个2*2的范围是比较合适的;
canvas.width = 2;
canvas.height = 2;
const ctx = canvas.getContext('2d');
ctx.textBaseline = 'top';
ctx.font = '100px sans-serif';
ctx.fillStyle = color;
ctx.scale(0.01, 0.01);
ctx.fillText(text, 0, 0);
const imageData = ctx.getImageData(0, 0, 2, 2).data;
// 在一些系统里Uint8ClampedArray不支持常规的数组方法,需要转换一下
const imageDataArr = [];
for (let i = 0; i < imageData.length; i++) {
imageDataArr[i] = imageData[i];
return imageDataArr.reduce((a, b) => (a + b), 0) > 0 ?
imageDataArr.toString() : false;
} catch (e) {
return false;
const distribute = (text, mode) => {
const feature = getTextFeature(text, '#000');
return mode ? (feature && feature === getTextFeature(text, '#FFF'))
: feature;
const ifEmoji = (text) => {
用一个最悠久而常见 emoji 来判断当前系统是使用图片还是字体来显示 emoji,
若是图片则去做上色比对,否则只对可见性做判断。
const mode = distribute('😁');
return distribute(text, mode);
export default ifEmoji;
Usage: