前端玩轉(zhuǎn) Emoji 表情符號(hào),看這一篇就夠了!
Emoji 已經(jīng)成為我們?nèi)粘贤ㄖ胁豢苫蛉钡囊徊糠?,本文就?lái)了解一下在前端中如何玩轉(zhuǎn) Emoji!
Emoji 基本概念
前置知識(shí)
在學(xué)習(xí) Emoji 之前,我們先來(lái)一些和 Emoji 相關(guān)的前置知識(shí)。
- Unicode:
a.定義: 一個(gè)國(guó)際標(biāo)準(zhǔn),旨在為世界上幾乎所有的字符(包括字母、數(shù)字、符號(hào)、標(biāo)點(diǎn)符號(hào)和表情符號(hào)等)提供唯一的數(shù)字表示形式。它的主要目標(biāo)是確保在不同的平臺(tái)、操作系統(tǒng)和設(shè)備之間能夠一致地表示和處理文本數(shù)據(jù)。
b.特點(diǎn):
統(tǒng)一編碼:Unicode 為每種語(yǔ)言中的每個(gè)字符分配了一個(gè)唯一的編號(hào)(稱為碼點(diǎn)),從而解決了不同編碼系統(tǒng)之間的不兼容問(wèn)題。
廣泛覆蓋:Unicode 包含了幾乎所有現(xiàn)代語(yǔ)言的字符,并且還在不斷擴(kuò)展以支持更多的符號(hào)和歷史文字。
多種實(shí)現(xiàn)方式:Unicode 支持多種編碼格式,如 UTF-8、UTF-16 和 UTF-32,以便在不同的應(yīng)用場(chǎng)景中靈活使用。
- 碼點(diǎn):
- 定義: 指在 Unicode 標(biāo)準(zhǔn)中為每個(gè)字符分配的一個(gè)唯一的數(shù)字值。它通常用十六進(jìn)制表示,并帶有前綴 U+ 來(lái)標(biāo)識(shí)這是一個(gè) Unicode 碼點(diǎn)。
- 舉例:
a.字母 A 的 Unicode 碼點(diǎn)是 U+0041。
b.笑臉 Emoji ?? 的 Unicode 碼點(diǎn)是 U+1F60A。
- 范圍: Unicode 定義了從 U+0000 到 U+10FFFF 的碼點(diǎn)范圍,總共可以表示超過(guò)一百萬(wàn)個(gè)字符。這個(gè)范圍分為以下幾部分:
基本多文種平面:從 U+0000 到 U+FFFF,包含最常見(jiàn)的字符。
輔助平面:從 U+10000 到 U+10FFFF,用于表示較少見(jiàn)的字符和 Emoji。
編碼方式: Unicode 有多種編碼方式,常見(jiàn)的有 UTF - 8、UTF - 16 和 UTF - 32。
UTF - 8:一種可變長(zhǎng)度的編碼方式,使用 1 到 4 個(gè)字節(jié)來(lái)表示一個(gè)字符。它對(duì) ASCII 字符采用單字節(jié)編碼,兼容 ASCII 標(biāo)準(zhǔn),因此在互聯(lián)網(wǎng)上得到了廣泛應(yīng)用。
UTF - 16:使用 2 個(gè)或 4 個(gè)字節(jié)來(lái)表示一個(gè)字符,常用于操作系統(tǒng)和編程語(yǔ)言中,如 Java 和 JavaScript 默認(rèn)使用 UTF - 16 編碼。
UTF - 32:固定使用 4 個(gè)字節(jié)來(lái)表示一個(gè)字符,編碼簡(jiǎn)單直接,但會(huì)占用較多的存儲(chǔ)空間。
Emoji 是什么?
Emoji 是一種圖形符號(hào),最初由日本電信運(yùn)營(yíng)商在 1990 年代引入,用于增強(qiáng)短信和網(wǎng)頁(yè)的表達(dá)能力。隨著時(shí)間的發(fā)展,Emoji 已經(jīng)成為通信中不可或缺的一部分,廣泛應(yīng)用于社交媒體、電子郵件、即時(shí)通訊工具等各種平臺(tái)。
Emoji 本質(zhì)上是 Unicode 字符集中的一部分,每個(gè) Emoji 都有一個(gè)對(duì)應(yīng)的唯一 Unicode 編碼。在最新的 Unicode 16.0 版本中,共指定了 3790 個(gè) Emoji 及其編碼。
圖片
Emoji 對(duì)應(yīng)的 Unicode 編碼:https://unicode.org/emoji/charts/full-emoji-list.html
在支持 Unicode 的環(huán)境中,如現(xiàn)代的文本編輯器、瀏覽器等,你可以直接輸入 Emoji 字符來(lái)使用它們。許多操作系統(tǒng)都提供了便捷的 Emoji 輸入方法,例如在 Windows 系統(tǒng)中,按下 win + . 組合鍵可以打開(kāi) Emoji 選擇器;在 macOS 系統(tǒng)中,按下 Fn + E 組合鍵可以打開(kāi) Emoji 輸入菜單。
圖片
不知道你有沒(méi)有發(fā)現(xiàn),在不同系統(tǒng)/應(yīng)用上,Emoji 長(zhǎng)的都不太一樣,這是為什么呢?其實(shí),Unicode 為每個(gè) Emoji 分配了唯一的碼點(diǎn),確保其在不同系統(tǒng)中代表相同的含義,但并沒(méi)有規(guī)定每個(gè) Emoji 的樣式。因此,每個(gè)系統(tǒng)/應(yīng)用都可以根據(jù)各自的設(shè)計(jì)風(fēng)格對(duì) Emoji 進(jìn)行設(shè)計(jì),所以就出現(xiàn)了多種風(fēng)格的 Emoji。
圖片
為了提高一致性,一些項(xiàng)目提供了標(biāo)準(zhǔn)化的 Emoji 集合,可以通過(guò)這些項(xiàng)目來(lái)自定義 Emoji 顯示,如 Twemoji:https://github.com/googlefonts/noto-emoji
需要格外注意的是:不同的文化對(duì) Emoji 的理解和使用可能有所不同。例如,某些 Emoji 在特定文化中可能有特殊的含義或象征意義。因此,在國(guó)際化應(yīng)用中,需要特別注意 Emoji 的使用,以避免誤解或冒犯。
Emoji 在前端的應(yīng)用
表示
HTML
- 直接插入 Emoji:可以在 HTML 文件中的任何文本位置直接插入 Emoji。
<p>今天天氣真好 ??</p>
- 使用 Unicode 編碼:如果無(wú)法直接輸入 Emoji 或者希望使用其 Unicode 編碼,可以使用 Unicode 轉(zhuǎn)義序列。
<p>今天天氣真好 ?</p>
注意:&#x 后面跟隨的是 Emoji 的 Unicode 碼點(diǎn)的十六進(jìn)制表示。例如,笑臉 ?? 的 Unicode 碼點(diǎn)是 U+1F60A,在 HTML 中表示為 😊。
CSS
- 使用 content 屬性: 可以通過(guò) CSS 的 ::before 或 ::after 偽元素以及 content 屬性來(lái)插入 Emoji。
.emoji-before::before {
content: "??"; /* 直接插入 Emoji */
}
.emoji-after::after {
content: "\1F60A"; /* 使用 Unicode 轉(zhuǎn)義序列 */
}
注意: 在 CSS 中使用 Unicode 轉(zhuǎn)義序列時(shí),不需要 &#x 前綴,直接使用 \ 加上十六進(jìn)制編碼即可。
由于 Emoji 是彩色字符,通常不需要額外的顏色設(shè)置。不過(guò),可以通過(guò)調(diào)整字體大小來(lái)控制 Emoji 的顯示大小。
h1 {
font-size: 2em; /* 將字體大小放大兩倍 */
}
JavaScript
在 JavaScript 中,可以通過(guò)以下方式來(lái)表示 Emoji:
- 直接插入 Emoji: 在現(xiàn)代 JavaScript 環(huán)境中,可直接在字符串里使用 Emoji 字符。因?yàn)楝F(xiàn)代編輯器和瀏覽器廣泛支持 Unicode 字符,能正確識(shí)別和顯示 Emoji。
console.log('Hello ??'); // 輸出: Hello ??
- 使用 Unicode 編碼: 每個(gè) Emoji 都有對(duì)應(yīng)的 Unicode 編碼,可通過(guò) Unicode 編碼來(lái)表示 Emoji。基本多文種平面(BMP)內(nèi)的 Emoji 用 \u 加 4 位十六進(jìn)制編碼表示;超出 BMP 的 Emoji 需用代理對(duì)或 \u{} 語(yǔ)法表示。
// BMP 內(nèi)的 Emoji
const heart = '\u2764';
console.log(heart); // ?
// 超出 BMP 的 Emoji,使用代理對(duì)
const rocket = '\uD83D\uDE80';
console.log(rocket); // ??
// 超出 BMP 的 Emoji,使用 \u{} 語(yǔ)法
const pizza = '\u{1F355}';
console.log(pizza); // ??
獲取和設(shè)置碼點(diǎn)
- 獲取碼點(diǎn): 可以使用 codePointAt() 方法來(lái)獲取字符串中某個(gè)位置的 Unicode 碼點(diǎn)。
const smiley = '??';
console.log(smiley.codePointAt(0).toString(16)); // 輸出: 1f60a
- 設(shè)置碼點(diǎn): 可以使用 String.fromCodePoint() 方法從 Unicode 碼點(diǎn)創(chuàng)建字符串。
console.log(String.fromCodePoint(0x1F60A)); // 輸出: ??
字符串操作
問(wèn)題
在 JavaScript 中處理 Emoji 時(shí),尤其是涉及字符串操作,存在一些常見(jiàn)的問(wèn)題。
- 代理對(duì): 在 JavaScript 里,字符串中的每個(gè)字符通常以 16 位(即 2 個(gè)字節(jié))來(lái)表示,這遵循的是 UTF-16 編碼規(guī)則。不過(guò),Emoji 字符比較特殊,部分 Emoji 字符的編碼超出了基本多文種平面(BMP),需要使用代理對(duì)來(lái)表示,也就是用兩個(gè) 16 位編碼單元來(lái)表示一個(gè) Emoji 字符。因此,普通的 .length 屬性可能會(huì)返回不正確的字符數(shù),因?yàn)樗鼤?huì)將代理對(duì)視為兩個(gè)字符。
const emoji = '??';
console.log(emoji.length); // 輸出: 2 (而不是1)
- 組合字符: 帶有修飾符的 Emoji 可能由多個(gè)碼點(diǎn)組成。例如,?????? 是由三個(gè)獨(dú)立的 Emoji 組合而成的。因此,在使用 .length計(jì)算長(zhǎng)度時(shí),得到的結(jié)果是不準(zhǔn)確的。
const familyEmoji = '??????';
console.log(familyEmoji.length); // 輸出: 8 (而不是1)
- 截?cái)鄦?wèn)題: JavaScript 中的 slice、substring 或 substr 方法會(huì)按照 16 位編碼單元來(lái)截取字符串,當(dāng)使用這些方式截?cái)喟?Emoji 的字符串時(shí),若在截取過(guò)程中恰好截?cái)嗔艘粋€(gè)代理對(duì),就會(huì)產(chǎn)生亂碼。因?yàn)檫@些方法是無(wú)法正確處理代理對(duì)和組合字符的。
const emojiStr = '??????';
// 錯(cuò)誤的截取,截?cái)嗔舜韺?duì)
const wrongSubStr = emojiStr.slice(0, 1);
console.log(wrongSubStr); // ?
內(nèi)置 API:Intl.Segmenter
ES2021 提供了 Intl.Segmenter,可以解決上面的問(wèn)題,它是 ECMAScript 國(guó)際化 API 的一部分,用于根據(jù)語(yǔ)言和區(qū)域設(shè)置對(duì)字符串進(jìn)行分割。它可以將文本分割成有意義的單元,如單詞、句子或圖元簇,從而更好地處理多語(yǔ)言文本。
Intl.Segmenter 提供了以下分割方式:
- Grapheme Cluster(圖元簇):將字符串分割為用戶感知的字符單位。這對(duì)于處理復(fù)雜的 Unicode 字符(如 Emoji 和組合字符)非常有用。
- Word(單詞):將字符串分割為單詞。
- Sentence(句子):將字符串分割為句子。
Intl.Segmenter 的基本用法如下:
- 'en':指定語(yǔ)言環(huán)境??梢愿鶕?jù)需要更改為其他語(yǔ)言環(huán)境,如 'zh' 或 'fr'。
- { granularity: 'grapheme' }:指定分割粒度,可以是 'grapheme'、'word' 或 'sentence'。
const segmenter = new Intl.Segmenter('en', { granularity: 'grapheme' });
在處理包含復(fù)雜 Unicode 字符(如 Emoji 和組合字符)的字符串時(shí),確保準(zhǔn)確計(jì)算字符數(shù)量。
const segmenter = new Intl.Segmenter([], { granularity: 'grapheme' });
const text = 'Hello ????♂? ??????!';
const segments = Array.from(segmenter.segment(text));
console.log(segments.length); // 輸出: 10
第三方庫(kù):grapheme-splitter
對(duì)于上面這些問(wèn)題,也可以使用grapheme-splitter庫(kù)來(lái)解決,它是一個(gè)用于處理 Unicode 字符串的 JavaScript 庫(kù),專門(mén)用于將字符串分割成“圖元簇”。圖元簇是用戶感知的一個(gè)字符單位,即使它可能由多個(gè) Unicode 碼點(diǎn)組成。例如,帶有膚色修飾符或性別修飾符的 Emoji 實(shí)際上是由多個(gè)碼點(diǎn)組成的,但用戶通常將其視為一個(gè)整體。
grapheme-splitter 的使用場(chǎng)景:
- 統(tǒng)計(jì)字符數(shù)量:當(dāng)需要準(zhǔn)確統(tǒng)計(jì)字符串中用戶實(shí)際看到的字符個(gè)數(shù)時(shí),例如在文本輸入框中限制字符數(shù)量,包含組合字符的字符串使用 .length 計(jì)算會(huì)不準(zhǔn)確,使用 grapheme-splitter 可以得到正確結(jié)果。
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", " ", "????♂?", " ", "??????", "!"]
// 獲取第一個(gè)圖元簇
console.log(graphemes[0]); // 輸出: H
// 獲取第7個(gè)圖元簇(包含復(fù)雜的Emoji)
console.log(graphemes[5]); // 輸出: ????♂?
// 獲取最后一個(gè)圖元簇
console.log(graphemes[graphemes.length - 1]); // 輸出: !
- 字符串截?。涸诮厝∽址畷r(shí),確保不會(huì)截?cái)嘟M合字符,避免出現(xiàn)亂碼或不完整的字符顯示。
import GraphemeSplitter from "grapheme-splitter";
const splitter = new GraphemeSplitter();
const text = 'Hello ????♂? ??????!';
const graphemes = splitter.splitGraphemes(text);
// 截取前5個(gè)圖元簇
const firstFive = graphemes.slice(0, 5).join("");
console.log(firstFive); // 輸出: Hello
// 截取第6到第7個(gè)圖元簇
const middlePart = graphemes.slice(6, 7).join("");
console.log(middlePart); // 輸出: ????♂?
// 截取最后1個(gè)圖元簇
const lastFive = graphemes.slice(-2).join("");
console.log(lastFive); // 輸出: ??????!
- 字符遍歷:按用戶感知的字符逐個(gè)遍歷字符串,對(duì)每個(gè)字符進(jìn)行特定操作。
import GraphemeSplitter from "grapheme-splitter";
const splitter = new GraphemeSplitter();
const str = '????????????♂?';
const graphemes = splitter.splitGraphemes(str);
graphemes.forEach((grapheme) => {
console.log(grapheme);
});
驗(yàn)證
在處理 Emoji 時(shí),驗(yàn)證輸入是否包含有效的 Emoji 字符是一個(gè)常見(jiàn)的需求。這可以通過(guò)多種方式實(shí)現(xiàn),包括正則表達(dá)式以及使用專門(mén)的庫(kù)來(lái)幫助識(shí)別和驗(yàn)證 Emoji。
正則表達(dá)式
Emoji 是 Unicode 字符,因此可以使用正則表達(dá)式來(lái)匹配它們。然而,由于 Emoji 的種類繁多,并且新的 Emoji 不斷被添加到 Unicode 標(biāo)準(zhǔn)中,編寫(xiě)一個(gè)全面的正則表達(dá)式可能會(huì)比較復(fù)雜。下面是一個(gè)例子:
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.');
}
// 驗(yàn)證是否包含 Emoji
const hasEmoji = emojiRegex.test(text);
console.log('Contains emoji:', hasEmoji); // 輸出: true
注意: 當(dāng)使用正則表達(dá)式處理包含超出 BMP 的 Unicode 字符(如 Emoji)的字符串時(shí),就需要使用 u 修飾符。u 修飾符的主要作用是開(kāi)啟 Unicode 模式,讓正則表達(dá)式能夠正確處理超過(guò) \uFFFF 的 Unicode 字符。在 JavaScript 里,字符串中的字符默認(rèn)以 UTF-16 編碼存儲(chǔ),基本多文種平面(BMP)內(nèi)的字符可以用一個(gè) 16 位的編碼單元表示,但超出 BMP 的字符需要用兩個(gè) 16 位的編碼單元(代理對(duì))來(lái)表示。在沒(méi)有 u 修飾符的情況下,正則表達(dá)式會(huì)將代理對(duì)拆分成兩個(gè)單獨(dú)的編碼單元進(jìn)行處理;而使用 u 修飾符后,正則表達(dá)式能將代理對(duì)視為一個(gè)整體,從而正確匹配和處理這些字符。
第三方庫(kù):emoji-regex
emoji-regex 是一個(gè)專門(mén)用于匹配 Emoji 的 JavaScript 庫(kù)。它可以準(zhǔn)確地識(shí)別和匹配各種類型的 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 字符。它在許多應(yīng)用(如聊天軟件、社交平臺(tái)、文本編輯器等)中廣泛使用。我們可以根據(jù)需要自定義 Emoji 選擇器(可以借助 emojibase 庫(kù)的數(shù)據(jù)來(lái)實(shí)現(xiàn)),也可以使用開(kāi)源的 Emoji 選擇器,以下是一些比較熱門(mén)的 Emoji 選擇器(npm包名稱)。
- emoji-mart:
圖片
- emoji-picker-react:
圖片
- vue3-emoji-picker:
圖片