前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何用JS识别用户浏览器是否支持某 Emoji?比如🧑‍🌾可能展示为🧑🌾

如何用JS识别用户浏览器是否支持某 Emoji?比如🧑‍🌾可能展示为🧑🌾

原创
作者头像
HullQin
发布2023-01-30 10:39:42
5.2K0
发布2023-01-30 10:39:42
举报

背景

之前我在文章《为什么同一表情'🧔‍♂️'.length==5但'🧔‍♂'.length==4?本文带你深入理解 String Unicode UTF8 UTF16》中讲了非常硬核的内容,深入带大家了解了 Unicode UTF8 以及 JavaScript 中的 String 字符串。非常推荐你仔细阅读并收藏。

如果你的网页中,展示一些 Emoji,那么一定要小心!因为 Emoji 也是在不断的更新迭代的,在旧的设备或系统中,可能无法正确地展示新出的 Emoji。

比较推荐的做法:要展示某个 Emoji 前,优先判断它是否能正确展示,如果不能展示,可以展示文字描述,或者替换为旧版类似的 Emoji,或者展示兜底图案。

问题来了:如何判断用户浏览器能否正确展示某个 Emoji?

解决思路

我们在用户看不到的地方,创建一个元素,不设置该元素的宽度,并把元素的内容设置为该 Emoji。

  • 如果该元素的宽度等于「正常展示 Emoji 时的宽度」,说明该 Emoji 可以正常展示。
  • 如果该元素的宽度大于「正常展示 Emoji 时的宽度」,说明该 Emoji 被拆开展示了。例如🧑‍🌾展示为🧑🌾。
  • 如果该元素的宽度小于「正常展示 Emoji 时的宽度」,说明不认识该 Emoji,可能展示为方框。

难点

  • 如何获取「正常展示 Emoji 时的宽度」?
  • 如何保证不影响用户体验?
  • 如何确保不存在字号问题?

解决方案

获取元素宽度

首先写个函数,创建包含某个文字的元素,并计算它的宽度。计算完宽度后,把该元素删除。

const getTextWidth = (text) => {
  const element = document.createElement('span');
  element.setAttribute('aria-hidden', 'true');
  element.setAttribute('style', 'font-size:16px!important;position:absolute;top:0;opacity:0;font-family:Consolas,"Liberation Mono","Courier New",monospace');
  element.textContent = text;
  document.body.appendChild(element);
  const width = element.clientWidth;
  document.body.removeChild(element);
  return width;
};

为了保证不影响用户体验,我们需要设置它为绝对定位,且它是透明的。而且要设置它的 aria-hidden ,保证屏幕阅读器不会聚焦到该元素。(详细了解该属性,请阅读:《什么是无障碍适配?》《Web如何适配无障碍?》

好处:这样即使用户电脑很卡,也不会看到这个元素了。而且由于该元素不影响用户页面的布局,不会触发浏览器的重排。

为了确保字号一致,影响判断,我设置了内联样式,并且加了 !important,使之具有最高优先级。

此外,我还设置了 font-familymonospace 这种等宽字体,主要目的是识别出方框,因为默认字体下即使字符展示为方框,它的宽度依旧跟「正常展示 Emoji 时的宽度」一致。

获取「正常展示 Emoji 时的宽度」

这里,我们使用一个兼容性最好的 Emoji,计算它的宽度。

如果该宽度大于等于字号(最多允许比16px的字号小一点点,比如允许小2px,那么就是必须大于等于14),就用这个值作为「正常展示 Emoji 时的宽度」。

const EmojiWidth = getTextWidth('😀');

检验 Emoji 能否被正常展示

const isEmojiValid = (emoji) => {
  return getTextWidth(emoji) === EmojiWidth && EmojiWidth >= 14;
};

一些兼容性做法

举个例子,我在我的《联机桌游合集》中,用户可以自定义 Emoji 当作头像,如果 iOS 用户设置了比较复杂的 Emoji,在 Android 上无法正常展示,那么就需要有兜底逻辑。我是这么做的:

  • 如果检测 Emoji 展示宽度大于正常宽度,认为是 1 个 Emoji 被分成了多个展示了,表明是当前系统不支持这个复杂的 Emoji。那么我只展示第一个,例如 🧑‍🌾 我在安卓上只展示 🧑。
  • 如果检测 Emoji 展示宽度小于正常宽度,认为是当前系统不支持该 Unicode 码,没有对应的符号,我直接留空即可,用户也知道是系统不支持(如果展示方框就比较丑)。
const showEmoji = (emoji) => {
  const width = getTextWidth(emoji);
  if (width > EmojiWidth + 3) return emoji.substring(0, 2);
  if (width < EmojiWidth - 3) return '';
  return emoji;
};

注意,这里我在比较时,有 +3 和 -3,是因为在 Windows 上,不同 Emoji 的宽度有微小差异,所以用 +3 -3 做了兼容处理。

文章《为什么同一表情'🧔‍♂️'.length==5但'🧔‍♂'.length==4?本文带你深入理解 String Unicode UTF8 UTF16》中我提到了 200D 这个 零宽连字符,几乎所有组合 Emoji 都是通过它组合的。

当浏览器不支持某个组合时,就会拆开展示。

通过我开发的工具 tool.hullqin.cn,可以看到该字符的 Unicode 对应 1F9D1 200D 1F33E,我们只展示第一个 Emoji 即 1F9D1 就行,所以用了 emoji.substring(0, 2)

取开头2个字符的原因,也在文章《为什么同一表情'🧔‍♂️'.length==5但'🧔‍♂'.length==4?本文带你深入理解 String Unicode UTF8 UTF16》里,因为 JS String 使用 UTF16 编码,它一个表情符号就占用了 2 长度。

写在最后

我是HullQin,公众号线下聚会游戏的作者(欢迎关注我,交个朋友)。转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩UNO、斗地主、五子棋、一夜狼、象棋等游戏,不收费无广告。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我噢~我有空了会分享做游戏的相关技术,会在这个专栏里分享:《教你做小游戏》

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 解决思路
  • 难点
  • 解决方案
    • 获取元素宽度
      • 获取「正常展示 Emoji 时的宽度」
        • 检验 Emoji 能否被正常展示
          • 一些兼容性做法
          • 写在最后
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档