emoji 存储及展示 前端完美解决方案

6,123 阅读2分钟

遇到的问题

最近在做一个论坛功能,发帖和评论输入都需要保存到数据库。 而测试测了emoji保存是有问题的,查了数据库是没存进去,google查了一下,emoji是4个字节的,需要将 MySql数据库字符集改由utf8改为utf8mb4,于是把找到的帖子给DBA,DBA看了说不能改,可能会影响现有的数据。

那就只好自己动手,丰衣足食了。
(如果下面的emoji没有正常展示请使用最新版的火狐浏览器)

emoji 存储及展示 解决思路

既然直接存储emoji有问题,就改为存储emoji的转换结果。 于是沿着这个思路,首先想到的是 charCodeAt/fromCharCode

const charCode = '☠️'.charCodeAt(0) // 10084
String.fromCharCode(charCode) // '☠️'

这么简单,完美!

如果我没遇到这个 🤣 的话

const charCode = '🤣'.charCodeAt(0) // 55358
String.fromCharCode(charCode) // '\ud83e'

What ???
让我们来看看 String.prototype.charCodeAt()

charCodeAt() 方法返回0到65535之间的整数,表示给定索引处的UTF-16代码单元 (在 Unicode 编码单元表示一个单一的 UTF-16 编码单元的情况下,UTF-16 编码单元匹配 Unicode 编码单元。但在——例如 Unicode 编码单元 > 0x10000 的这种——不能被一个 UTF-16 编码单元单独表示的情况下,只能匹配 Unicode 代理对的第一个编码单元) 。如果你想要整个代码点的值,使用 codePointAt()。

上面 fromCharCode 得到 \ud83e 的原因是 🤣 由两个编码单元组成,上面引用 MDN 的这段话也推荐了我们使用另一个API codePointAt。 而与之对应的 fromCharCode 则相应变成要 fromCodePoint。

const charCode = '🤣'.codePointAt(0) // 129315
String.fromCodePoint(charCode) // '🤣'

完美解决由🤣这类emoji产生的问题。

实际问题解决方案

现在用户输入了下面这段字符串:

const str = 'hello world 🤣💯!🙌'

我们可以将它转化为:

const emojiRegex = require('emoji-regex')
const regex = emojiRegex()
const fmt_str = str.replace(regex, (p) => `emoji(${p.codePointAt(0)})`) 
console.log(fmt_str) // hello world emoji(129315)emoji(128175)!emoji(128588)

利用正则 emoji-regex 匹配出字符串中所有的 emoji ,然后转换。
实际存到数据库里的是 fmt_str 从数据库取出来前端展示时:

const emojiDecodeRegex = /emoji\(\d+\)/g
const ori_str = fmt_str.replace(emojiDecodeRegex, p => {
  const filterP = p.replace(/[^\d]/g, '')
  return String.fromCodePoint(filterP)
})
console.log(ori_str) // hello world 🤣💯!🙌

总结

除了转换成 emoji(129315) 这样的形式,还可以转换成其它的形式;只要是用户输入可能性非常小的就好。 就算是微信,输入: [鼓掌],并发送也一样会变成对应的 emoji。

最后将其封装为两个方法,方便后续多个地方调用

const emojiRegex = require('emoji-regex')

const encodeEmoji = str => {
  const regex = emojiRegex()
  return str.replace(regex, p => `emoji(${p.codePointAt(0)})`)
}

const deCodeEmoji = str => {
  const emojiDecodeRegex = /emoji\(\d+\)/g
  return str.replace(emojiDecodeRegex, p => {
    const filterP = p.replace(/[^\d]/g, '')
    return String.fromCodePoint(filterP)
  })
}