likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 10

背景

之前我在文章《为什么同一表情'🧔‍♂️'.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;display:absolute;top:0;opacity:0;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,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;
};

写在最后