之前我在文章《为什么同一表情'🧔♂️'.length==5但'🧔♂'.length==4?本文带你深入理解 String Unicode UTF8 UTF16》中讲了非常硬核的内容,深入带大家了解了 Unicode UTF8 以及 JavaScript 中的 String 字符串。非常推荐你仔细阅读并收藏。
如果你的网页中,展示一些 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-family
为 monospace
这种等宽字体,主要目的是识别出方框,因为默认字体下即使字符展示为方框,它的宽度依旧跟「正常展示 Emoji 时的宽度」一致。
这里,我们使用一个兼容性最好的 Emoji,计算它的宽度。
如果该宽度大于等于字号(最多允许比16px的字号小一点点,比如允许小2px,那么就是必须大于等于14),就用这个值作为「正常展示 Emoji 时的宽度」。
const EmojiWidth = getTextWidth('😀');
const isEmojiValid = (emoji) => {
return getTextWidth(emoji) === EmojiWidth && EmojiWidth >= 14;
};
举个例子,我在我的《联机桌游合集》中,用户可以自定义 Emoji 当作头像,如果 iOS 用户设置了比较复杂的 Emoji,在 Android 上无法正常展示,那么就需要有兜底逻辑。我是这么做的:
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 删除。