部分emoji表情在手机上显示不出来,什么情况?

4,710 阅读11分钟

背景

上周在工作中遇到一个历史遗留缺陷,在管理后台配置的emoji表情,在有些手机上,部分emoji图标显示成方框格子,这个问题要是放在平时比较忙碌的时候,随便找个理由就能把产品和测试糊弄过去,但上周工作不算饱和,工作不饱和的时候,人的求知欲就比较旺盛,于是决定探究一下。这是后端接口返回的emoji编码:

{
  "ret": 0,
  "retmsg": "ok",
  "retdata": {
    "content": [
      {
        // ...
        "momentText": "\uD83D\uDE43\uD83E\uDD17\uD83E\uDD10\uD83D\uDE1C\uD83D\uDE2A\uD83E\uDD12\uD83E\uDD12\uD83E\uDDB7\uD83E\uDDB5\uD83E\uDD1B\uD83D\uDC48\uD83E\uDD1A\uD83D\uDE40\uD83D\uDE48\uD83D\uDC47\uD83D\uDC9C\uD83D\uDC97\uD83E\uDDE1\uD83E\uDD0E\uD83D\uDCA2\uD83E\uDD4B\uD83C\uDFD3\uD83E\uDD4E\uD83C\uDF81\uD83C\uDFB6\uD83C\uDFB6\uD83C\uDFBC\uD83D\uDD14\uD83E\uDE93\uD83E\uDE79\uD83E\uDDEC♉♌\uD83D\uDEC4\uD83D\uDD1B\uD83D\uDFE0\uD83D\uDD34\uD83D\uDFE8\uD83D\uDD39\uD83C\uDE50\n天天好心情",
      },
    ],
  },
}

这是正常的显示:

image.png

这是部分emoji图标显示成方框的情形:

image.png

emoji的前世今生

1999年从日本的手机中诞生emoji,emoji这个词在日语里的含义是“绘文字”。 “绘”对应“e”,“文字”对应“moji”。第一版emoji大部分是表达天气、交通、科技的符号,而非表情。如今已经成为社交软件中表达丰富感情时不可缺少的存在。emoji从诞生之日起就一直被一个问题所困扰,在不同的平台,同样的表情,展示效果看起来并不一样,没有标准化。不同的平台都根据本平台的审美来解读与设计 emoji 表情。苹果的设计风格特征是立体,渐变和阴影的细节比较考究;Google 则是更加轻松和卡通化; 微软喜欢又黑又粗的边框,可能是想与文字明显区分开来;Facebook(现在的Meta)的emoji表情表现力比较强,Twitter的emoji非常简洁,基本没有阴影渐变这些细节。如下图所示,同样一个翻白眼的表情,在每个平台看起来差别还是比较明显的。

image.png

emoji与unicode的关系

每一个 emoji,就是一个 Unicode 字符。全世界的 emoji 都由统一码联盟(The Unicode Consortium)来投票选拔和公布,世界各地的人们可以向联盟提交 emoji 提案。而统一码联盟的 emoji 规范,只是定义了某个字符的语义,再由 Emojipedia 这个网站对 emoji 进行描述表达,最后允许大家按照对描述的理解,自由地去设计图案。

从2010年开始,Unicode 开始为 Emoji 分配码点。比如:U+1F4C5,U+1F468, U+1F600等。Unicode标准规定emoji字符的Code Point范围为U+1F601 ~ U+1F64F。 从这个网站可以查到emoji2023年9月16日发布的最新版本是15.1,截止目前为止共有4102个字符。

Unicode 只是规定了 Emoji 的码点和含义,并没有规定它的外观展示。举例来说,码点U+1F600表示一张微笑的脸,但是这张微笑的脸表情长什么样,则由各个平台(Windows,Mac,Linux, IOS, Android等)自己实现。如果用户的系统没有实现这个Emoji符号,用户就会看到一个没有内容的方框(如下图所示,三者必居其一,不光是emoji表情,其它类型的字体图标渲染不出来的时候也是这样的展示),因为系统无法渲染这个码点。

image.png image.png image.png

因为PC端浏览器默认会自动更新,所以emoji的表情字体库能得到及时更新, 相对而言,升级移动设备上的操作系统版本,成本要高一些,所以移动端操作系统的emoji字体库,一般都没有最新的emoji字体码点,这就造成在PC端展示正常的emoji表情,在低版本的Android或IOS系统上,可能会展示不出来。

emoji组合

除了使用单个Code Point表示一个emoji字符外, 还允许多个Code Point组合在一起表示一个emoji字符。 如何组合呢?其中的一种方式是零宽度连接符U+200D(下文会讲)。

举个例子,下面是三个emojiCode Point

U+1F468:男人
U+1F469:女人
U+1F467:女孩

上面三个Code Point使用U+200D连接起来,U+1F468 U+200D U+1F469 U+200D U+1F467, 就会显示为一个emoji表情:👨‍👩‍👧,表示他们组成的家庭。如果用户的系统不支持这种方法,就还是显示为三个独立的emoji表情: 👨👩👧

可以用零宽字符连接emoji表情,那么什么是零宽字符(Zero-width character),零宽字符是指 Unicode 中那些不包含任何可见图形的字符,当它们被显示出来时,宽度为零。

常见的零宽字符有:

  • 零宽空格(Zero-width space,U+200B) - 用于字词间添加间隔,不换行。
  • 零宽连字号(Zero-width joiner,U+200D) - 用于连接两个表意文字(emoji等),组合成一个字符。
  • 零宽不连字号(Zero-width non-joiner,U+200C) - 用于阻止两个表意文字连写。
  • 零宽度省略号(Zero width ellipsis, U+2060) - 表示省略号,但不显示出来。

合理运用零宽字符,可以实现很多文本编排的特殊效果。零宽字符的用途有:

  • 为文本添加间距,但不影响文本布局。
  • 连接或阻止表意文字的组合。
  • 作为空文档的占位符。
  • 辅助排版格式。
  • 防止字符转换成表意文字。
  • 标记文本片段而不显示标记。

解决问题

知道了emoji表情为什么显示不出来的原因,解决方案也就水落石出。

  • 下载emoji字体到自己的应用中 从网络下载所需的emoji表情字体,这样就可以摆脱对平台emoji字体的依赖。(靠谱)
  • 升级操作系统 新版本的操作系统通常会包含更完整的emoji表情支持。(代价高)
  • 替换为图片 可以将emoji表情替换为等效的图片来表示。(说易行难)。
  • 安装emoji表情包 有些手机厂商会提供额外的emoji表情包用于扩展默认的表情集。可以在应用商店搜索并安装适用于手机的emoji表情包。(不通用)

这里选择了可行性比较强的第一种方案,解决部分emoji表情在低版本手机上无法显示的问题。出现问题的项目是个H5应用,所以采用Web开发技术中的css自定义字体解决这个问题。使用@font-face自定义字体时,通常需要加载好几种字体格式,以前不是很清楚这几种字体格式的区别,趁着这次机会,把这一块也梳理一下。

常见的字体类型

常见的字体类型如下表所示,目前现代浏览器基本都支持.ttfotfwoff,.woff2,.svg字体格式,为了保证兼容不同的浏览器,一般会同时指定多个格式

字体类型说明
.ttfTrue Type,是Windows和Mac系统最常用的字体格式,它可以任意缩放和旋转不会出现锯齿,随着 windows 的流行,已经变成最常用的一种字体文件表示方式,这种格式的字体文件体积比较大,字体文件可以达到 24MB+,通常只用作安装到计算机中的字体,或者在网页中设备不支持 WOFF2 字体时兜底。
.otfOpen Type,可缩放性的电脑字体类型,是微软与Adobe共同开发用来替代TrueType的新字形,微软的IE浏览器全部采用这种字体。致力于替代TrueType字体。OTF 字体文件体积也很大,基本和 TTF 差不多。
.eotEmbedded Open Type,微软开发的嵌入字体格式,允许OpenType字体用@font-face嵌入到网页并下载至浏览器渲染,存储在临时安装文件夹下。
.woffWeb Open Font Format,专门为了We设计的字体格式标准,是对True Type、Open Type等字体格式的封装,体积非常小
.woff2WOFF 1.0使用zlib压缩,文件大小一般比TTF小40%。而WOFF 2.0使用Brotli压缩,文件大小比上一版小30%。
.svgScalable Vector Graphics Fonts,使用SVG来呈现字体(.svgz是使用了Gzip压缩的SVG字体),这种字体是非常早期的标准,已经不推荐使用

由于笔者的手机是Android系统,所以需要下载的字体是Google派系的Noto Color Emoji字体, 点击这里下载。下载的时候要注意,要下载的是Noto Color Emoji而非Noto Emoji, 这两者的区别是前者的emoji图标是彩色的,图标很全面,字体资源文件体积比较大,接近24M,后者的emoji图标是黑色的,图标也不全。笔者刚开始下载的是Noto Emoji字体,发现原来不显示的emoji表情依旧不显示,经过一番排查,才发现要下载的emoji字体文件的名称应该是Noto Color Emoji

另外大家有没有发现一个问题,就是ttf字体文件体积很大,这会影响页面的打开和响应事件的速度。因此需要对ttf文件进行压缩,可以采用在线压缩工具,也可以采用命令行工具(font-spider,fontforge等),因为woff2格式的字体体积比较小,所以在网页端使用的话,一般使用的都是woff或者woff2格式。

运行测试

现在我们写一个测试程序,测试一下能否通过加载本地的字体资源文件的方式,让个别原本不能显示的emoji表情能正常显示。测试文件内容如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>emoji显示测试</title>
    <script src="http://cdn.staticfile.org/jquery/2.1.0/jquery.min.js"></script>

    <style>
      @font-face {
        font-family: emoji;
        src: url("./NotoColorEmoji-Regular.woff2") format('woff2');
      }

      .emoji {
        font-family: emoji;
      }
    </style>
  </head>
  <body>
    <pre class="emoji"></pre>
    <script>
      const momentText = "\uD83D\uDE43\uD83E\uDD17\uD83E\uDD10\uD83D\uDE1C\uD83D\uDE2A\uD83E\uDD12\uD83E\uDD12\uD83E\uDDB7\uD83E\uDDB5\uD83E\uDD1B\uD83D\uDC48\uD83E\uDD1A\uD83D\uDE40\uD83D\uDE48\uD83D\uDC47\uD83D\uDC9C\uD83D\uDC97\uD83E\uDDE1\uD83E\uDD0E\uD83D\uDCA2\uD83E\uDD4B\uD83C\uDFD3\uD83E\uDD4E\uD83C\uDF81\uD83C\uDFB6\uD83C\uDFB6\uD83C\uDFBC\uD83D\uDD14\uD83E\uDE93\uD83E\uDE79\uD83E\uDDEC♉♌\uD83D\uDEC4\uD83D\uDD1B\uD83D\uDFE0\uD83D\uDD34\uD83D\uDFE8\uD83D\uDD39\uD83C\uDE50\n天天好心情";

    </script>
  </body>
</html>

用VScode中的Live Server扩展启动这个页面,在手机上打开,如下图所示,原来不能显示的emoji表情已经可以正常显示了。

image.png

版权问题

有人可能会说,Android平台的emoji字体图标不如IOS平台的emoji字体图标好看,为什么不在所有的平台,使用Apple的emoji字体展示emoji表情呢。这是因为,许多字体都有版权,商用的话要付费,而Google Android的Noto Color Emoji字体,在Android平台肯定不会涉及版权问题。同理在IOS系统上,下载使用Apple-Color-Emoji字体,也不会涉及版本问题。

后记

本文发表两天后,意外的发现,掘金App,也存在着同样的问题。无图无真相,各位看官请看:

7342967369da98c5c899630a6c049b4.png

掘金的前端同学,趁着测试还没发现,赶紧修复一下吧,因为这个小问题拉低了自己的KPI,不能升职加薪,就太可惜了。