前端玩转 Emoji 表情符号,看这一篇就够了!

开发 前端
Emoji 是一种图形符号,最初由日本电信运营商在 1990 年代引入,用于增强短信和网页的表达能力。随着时间的发展,Emoji 已经成为通信中不可或缺的一部分,广泛应用于社交媒体、电子邮件、即时通讯工具等各种平台。

Emoji 已经成为我们日常沟通中不可或缺的一部分,本文就来了解一下在前端中如何玩转 Emoji!

Emoji 基本概念

前置知识

在学习 Emoji 之前,我们先来一些和 Emoji 相关的前置知识。

  • Unicode:

a.定义: 一个国际标准,旨在为世界上几乎所有的字符(包括字母、数字、符号、标点符号和表情符号等)提供唯一的数字表示形式。它的主要目标是确保在不同的平台、操作系统和设备之间能够一致地表示和处理文本数据。

b.特点:

统一编码:Unicode 为每种语言中的每个字符分配了一个唯一的编号(称为码点),从而解决了不同编码系统之间的不兼容问题。

广泛覆盖:Unicode 包含了几乎所有现代语言的字符,并且还在不断扩展以支持更多的符号和历史文字。

多种实现方式:Unicode 支持多种编码格式,如 UTF-8、UTF-16 和 UTF-32,以便在不同的应用场景中灵活使用。

  • 码点:
  • 定义: 指在 Unicode 标准中为每个字符分配的一个唯一的数字值。它通常用十六进制表示,并带有前缀 U+ 来标识这是一个 Unicode 码点。
  • 举例:

a.字母 A 的 Unicode 码点是 U+0041。

b.笑脸 Emoji 😊 的 Unicode 码点是 U+1F60A。

  • 范围: Unicode 定义了从 U+0000 到 U+10FFFF 的码点范围,总共可以表示超过一百万个字符。这个范围分为以下几部分:
  • 基本多文种平面:从 U+0000 到 U+FFFF,包含最常见的字符。

  • 辅助平面:从 U+10000 到 U+10FFFF,用于表示较少见的字符和 Emoji。

  • 编码方式: Unicode 有多种编码方式,常见的有 UTF - 8、UTF - 16 和 UTF - 32。

  • UTF - 8:一种可变长度的编码方式,使用 1 到 4 个字节来表示一个字符。它对 ASCII 字符采用单字节编码,兼容 ASCII 标准,因此在互联网上得到了广泛应用。

  • UTF - 16:使用 2 个或 4 个字节来表示一个字符,常用于操作系统和编程语言中,如 Java 和 JavaScript 默认使用 UTF - 16 编码。

  • UTF - 32:固定使用 4 个字节来表示一个字符,编码简单直接,但会占用较多的存储空间。

Emoji 是什么?

Emoji 是一种图形符号,最初由日本电信运营商在 1990 年代引入,用于增强短信和网页的表达能力。随着时间的发展,Emoji 已经成为通信中不可或缺的一部分,广泛应用于社交媒体、电子邮件、即时通讯工具等各种平台。

Emoji 本质上是 Unicode 字符集中的一部分,每个 Emoji 都有一个对应的唯一 Unicode 编码。在最新的 Unicode 16.0 版本中,共指定了 3790 个 Emoji 及其编码。

图片图片

Emoji 对应的 Unicode 编码:https://unicode.org/emoji/charts/full-emoji-list.html

在支持 Unicode 的环境中,如现代的文本编辑器、浏览器等,你可以直接输入 Emoji 字符来使用它们。许多操作系统都提供了便捷的 Emoji 输入方法,例如在 Windows 系统中,按下 win + . 组合键可以打开 Emoji 选择器;在 macOS 系统中,按下 Fn + E 组合键可以打开 Emoji 输入菜单。

图片图片

不知道你有没有发现,在不同系统/应用上,Emoji 长的都不太一样,这是为什么呢?其实,Unicode 为每个 Emoji 分配了唯一的码点,确保其在不同系统中代表相同的含义,但并没有规定每个 Emoji 的样式。因此,每个系统/应用都可以根据各自的设计风格对 Emoji 进行设计,所以就出现了多种风格的 Emoji。

图片图片

为了提高一致性,一些项目提供了标准化的 Emoji 集合,可以通过这些项目来自定义 Emoji 显示,如 Twemoji:https://github.com/googlefonts/noto-emoji

需要格外注意的是:不同的文化对 Emoji 的理解和使用可能有所不同。例如,某些 Emoji 在特定文化中可能有特殊的含义或象征意义。因此,在国际化应用中,需要特别注意 Emoji 的使用,以避免误解或冒犯。

Emoji 在前端的应用

表示

HTML

  • 直接插入 Emoji:可以在 HTML 文件中的任何文本位置直接插入 Emoji。
<p>今天天气真好 🌞</p>
  • 使用 Unicode 编码:如果无法直接输入 Emoji 或者希望使用其 Unicode 编码,可以使用 Unicode 转义序列。
<p>今天天气真好 ☀</p>

注意:&#x 后面跟随的是 Emoji 的 Unicode 码点的十六进制表示。例如,笑脸 😊 的 Unicode 码点是 U+1F60A,在 HTML 中表示为 &#x1F60A;。

CSS

  • 使用 content 属性: 可以通过 CSS 的 ::before 或 ::after 伪元素以及 content 属性来插入 Emoji。
.emoji-before::before {
  content: "😊"; /* 直接插入 Emoji */
}

.emoji-after::after {
  content: "\1F60A"; /* 使用 Unicode 转义序列 */
}

注意: 在 CSS 中使用 Unicode 转义序列时,不需要 &#x 前缀,直接使用 \ 加上十六进制编码即可。

由于 Emoji 是彩色字符,通常不需要额外的颜色设置。不过,可以通过调整字体大小来控制 Emoji 的显示大小。

h1 {
    font-size: 2em; /* 将字体大小放大两倍 */
}

JavaScript

在 JavaScript 中,可以通过以下方式来表示 Emoji:

  • 直接插入 Emoji: 在现代 JavaScript 环境中,可直接在字符串里使用 Emoji 字符。因为现代编辑器和浏览器广泛支持 Unicode 字符,能正确识别和显示 Emoji。
console.log('Hello 😊'); // 输出: Hello 😊
  • 使用 Unicode 编码: 每个 Emoji 都有对应的 Unicode 编码,可通过 Unicode 编码来表示 Emoji。基本多文种平面(BMP)内的 Emoji 用 \u 加 4 位十六进制编码表示;超出 BMP 的 Emoji 需用代理对或 \u{} 语法表示。
// BMP 内的 Emoji
const heart = '\u2764'; 
console.log(heart);   // ❤

// 超出 BMP 的 Emoji,使用代理对
const rocket = '\uD83D\uDE80'; 
console.log(rocket);  // 🚀

// 超出 BMP 的 Emoji,使用 \u{} 语法
const pizza = '\u{1F355}'; 
console.log(pizza);   // 🍕

获取和设置码点

  • 获取码点: 可以使用 codePointAt() 方法来获取字符串中某个位置的 Unicode 码点。
const smiley = '😊';
console.log(smiley.codePointAt(0).toString(16)); // 输出: 1f60a
  • 设置码点: 可以使用 String.fromCodePoint() 方法从 Unicode 码点创建字符串。
console.log(String.fromCodePoint(0x1F60A)); // 输出: 😊

字符串操作

问题

在 JavaScript 中处理 Emoji 时,尤其是涉及字符串操作,存在一些常见的问题。

  • 代理对: 在 JavaScript 里,字符串中的每个字符通常以 16 位(即 2 个字节)来表示,这遵循的是 UTF-16 编码规则。不过,Emoji 字符比较特殊,部分 Emoji 字符的编码超出了基本多文种平面(BMP),需要使用代理对来表示,也就是用两个 16 位编码单元来表示一个 Emoji 字符。因此,普通的 .length 属性可能会返回不正确的字符数,因为它会将代理对视为两个字符。
const emoji = '😊';
console.log(emoji.length); // 输出: 2 (而不是1)
  • 组合字符: 带有修饰符的 Emoji 可能由多个码点组成。例如,👨👩👧 是由三个独立的 Emoji 组合而成的。因此,在使用 .length计算长度时,得到的结果是不准确的。
const familyEmoji = '👨👩👧';
console.log(familyEmoji.length); // 输出: 8 (而不是1)
  • 截断问题: JavaScript 中的 slice、substring 或 substr 方法会按照 16 位编码单元来截取字符串,当使用这些方式截断包含 Emoji 的字符串时,若在截取过程中恰好截断了一个代理对,就会产生乱码。因为这些方法是无法正确处理代理对和组合字符的。
const emojiStr = '😀😃😄';
// 错误的截取,截断了代理对
const wrongSubStr = emojiStr.slice(0, 1); 
console.log(wrongSubStr); // �

内置 API:Intl.Segmenter

ES2021 提供了 Intl.Segmenter,可以解决上面的问题,它是 ECMAScript 国际化 API 的一部分,用于根据语言和区域设置对字符串进行分割。它可以将文本分割成有意义的单元,如单词、句子或图元簇,从而更好地处理多语言文本。

Intl.Segmenter 提供了以下分割方式:

  • Grapheme Cluster(图元簇):将字符串分割为用户感知的字符单位。这对于处理复杂的 Unicode 字符(如 Emoji 和组合字符)非常有用。
  • Word(单词):将字符串分割为单词。
  • Sentence(句子):将字符串分割为句子。

Intl.Segmenter 的基本用法如下:

  • 'en':指定语言环境。可以根据需要更改为其他语言环境,如 'zh' 或 'fr'。
  • { granularity: 'grapheme' }:指定分割粒度,可以是 'grapheme'、'word' 或 'sentence'。
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });

在处理包含复杂 Unicode 字符(如 Emoji 和组合字符)的字符串时,确保准确计算字符数量。

const segmenter = new Intl.Segmenter([], { granularity: 'grapheme' });
const text = 'Hello 🤦🏻♂️ 👨👩👧!';
const segments = Array.from(segmenter.segment(text));
console.log(segments.length); // 输出: 10

第三方库:grapheme-splitter

对于上面这些问题,也可以使用grapheme-splitter库来解决,它是一个用于处理 Unicode 字符串的 JavaScript 库,专门用于将字符串分割成“图元簇”。图元簇是用户感知的一个字符单位,即使它可能由多个 Unicode 码点组成。例如,带有肤色修饰符或性别修饰符的 Emoji 实际上是由多个码点组成的,但用户通常将其视为一个整体。

grapheme-splitter 的使用场景:

  • 统计字符数量:当需要准确统计字符串中用户实际看到的字符个数时,例如在文本输入框中限制字符数量,包含组合字符的字符串使用 .length 计算会不准确,使用 grapheme-splitter 可以得到正确结果。
import GraphemeSplitter from "grapheme-splitter";
const splitter = new GraphemeSplitter();

const str = '👨👩👧';
const length = splitter.countGraphemes(str);
console.log(length); // 输出:1
  • 从字符串中提取特定位置的图元簇:
import GraphemeSplitter from "grapheme-splitter";
const splitter = new GraphemeSplitter();

const text = 'Hello 🤦🏻♂️ 👨👩👧!';
const graphemes = splitter.splitGraphemes(text);
console.log(graphemes);  // 输出:["H", "e", "l", "l", "o", " ", "🤦🏻♂️", " ", "👨👩👧", "!"]

// 获取第一个图元簇
console.log(graphemes[0]); // 输出: H

// 获取第7个图元簇(包含复杂的Emoji)
console.log(graphemes[5]); // 输出: 🤦🏻♂️

// 获取最后一个图元簇
console.log(graphemes[graphemes.length - 1]); // 输出: !
  • 字符串截取:在截取字符串时,确保不会截断组合字符,避免出现乱码或不完整的字符显示。
import GraphemeSplitter from "grapheme-splitter";
const splitter = new GraphemeSplitter();

const text = 'Hello 🤦🏻♂️ 👨👩👧!';
const graphemes = splitter.splitGraphemes(text);


// 截取前5个图元簇
const firstFive = graphemes.slice(0, 5).join("");
console.log(firstFive); // 输出: Hello

// 截取第6到第7个图元簇
const middlePart = graphemes.slice(6, 7).join("");
console.log(middlePart); // 输出: 🤦🏻♂️

// 截取最后1个图元簇
const lastFive = graphemes.slice(-2).join("");
console.log(lastFive); // 输出: 👨👩👧!
  • 字符遍历:按用户感知的字符逐个遍历字符串,对每个字符进行特定操作。
import GraphemeSplitter from "grapheme-splitter";
const splitter = new GraphemeSplitter();

const str = '👨👩👧😃🤦🏻♂️';
const graphemes = splitter.splitGraphemes(str);
graphemes.forEach((grapheme) => {
    console.log(grapheme);
});

验证

在处理 Emoji 时,验证输入是否包含有效的 Emoji 字符是一个常见的需求。这可以通过多种方式实现,包括正则表达式以及使用专门的库来帮助识别和验证 Emoji。

正则表达式

Emoji 是 Unicode 字符,因此可以使用正则表达式来匹配它们。然而,由于 Emoji 的种类繁多,并且新的 Emoji 不断被添加到 Unicode 标准中,编写一个全面的正则表达式可能会比较复杂。下面是一个例子:

const emojiRegex = /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?(?:\u200d(?:[^\ud800-\udfff]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff])[\ufe0e\ufe0f]?(?:[\u0300-\u036f\ufe20-\ufe23\u20d0-\u20f0]|\ud83c[\udffb-\udfff])?)*/gu;

const text = "Hello 🤦🏻♂️ 👨👩👧! How are you?";

// 提取所有匹配的 Emoji
const matches = text.match(emojiRegex);

if (matches) {
    console.log('Matches:', matches); // 输出: ["🤦🏻♂️", "👨👩👧"]
} else {
    console.log('No emojis found.');
}

// 验证是否包含 Emoji
const hasEmoji = emojiRegex.test(text);
console.log('Contains emoji:', hasEmoji); // 输出: true

注意: 当使用正则表达式处理包含超出 BMP 的 Unicode 字符(如 Emoji)的字符串时,就需要使用 u 修饰符。u 修饰符的主要作用是开启 Unicode 模式,让正则表达式能够正确处理超过 \uFFFF 的 Unicode 字符。在 JavaScript 里,字符串中的字符默认以 UTF-16 编码存储,基本多文种平面(BMP)内的字符可以用一个 16 位的编码单元表示,但超出 BMP 的字符需要用两个 16 位的编码单元(代理对)来表示。在没有 u 修饰符的情况下,正则表达式会将代理对拆分成两个单独的编码单元进行处理;而使用 u 修饰符后,正则表达式能将代理对视为一个整体,从而正确匹配和处理这些字符。

第三方库:emoji-regex

emoji-regex 是一个专门用于匹配 Emoji 的 JavaScript 库。它可以准确地识别和匹配各种类型的 Emoji。

import emojiRegex from 'emoji-regex';
const regex = emojiRegex();

const text = "Hello 🤦🏻♂️ 👨👩👧! How are you?";
const matches = text.match(regex);

console.log(matches); // 输出: ["🤦", "🏻", "\u200d", "♂", "\ufe0f", "👨", "\u200d", "👩", "\u200d", "👧"]

Emoji 选择器

Emoji 选择器可帮助用户方便地插入 Emoji 字符。它在许多应用(如聊天软件、社交平台、文本编辑器等)中广泛使用。我们可以根据需要自定义 Emoji 选择器(可以借助 emojibase 库的数据来实现),也可以使用开源的 Emoji 选择器,以下是一些比较热门的 Emoji 选择器(npm包名称)。

  • emoji-mart:

图片图片

  • emoji-picker-react:

图片图片

  • vue3-emoji-picker:

图片图片

责任编辑:武晓燕 来源: 前端充电宝
相关推荐

2022-06-20 09:01:23

Git插件项目

2023-02-10 09:04:27

2020-02-18 16:20:03

Redis ANSI C语言日志型

2022-08-01 11:33:09

用户分析标签策略

2021-04-08 07:37:39

队列数据结构算法

2023-09-11 08:13:03

分布式跟踪工具

2020-08-03 10:00:11

前端登录服务器

2020-07-03 08:21:57

Java集合框架

2018-05-22 08:24:50

PythonPyMongoMongoDB

2019-05-14 09:31:16

架构整洁软件编程范式

2024-09-23 08:00:00

消息队列MQ分布式系统

2023-10-17 08:15:28

API前后端分离

2022-04-07 10:39:21

反射Java安全

2017-03-11 22:19:09

深度学习

2023-11-18 09:30:42

模型AI

2022-05-19 08:28:19

索引数据库

2019-04-01 10:43:59

Linux问题故障

2022-07-06 12:07:06

Python函数式编程

2020-10-21 14:12:02

Single Sign

2023-11-06 07:21:13

内存结构Jvm
点赞
收藏

51CTO技术栈公众号