﻿# 用 JavaScript 玩轉 Emoji 表情圖示原來這麼簡單 😃

這陣子花了一些時間研究 Unicode 萬國碼，看著看著就研究起 Emoji 表情圖示來了。過程中還跑去看了 [Unicode® Emoji](http://unicode.org/emoji/) 官方規格，要是沒有先建立對 Unicode 的理解，要完全看懂 Emoji 的來龍去脈是不太容易的。研究的過程中，還發現了一些有趣的冷知識，欲知詳情請繼續看下去！ 😉

首先，先來講怎樣在文件中使用 Emoji 表情圖示。這過程說簡單很簡單，要說複雜其實也有點複雜。基本上，你有 4 種方式可以在文件中輸出 Emoji 表情圖示：

1.  直接輸入 Emoji 字元
2.  將 12 個按鍵符號轉換為 Emoji 圖示
3.  在現有的 Emoji 字元後方加入菲茨派屈克修飾符 (膚色深度)
4.  透過 [零寬連字(ZWJ)](https://en.wikipedia.org/wiki/Zero-width_joiner) 將多個 Emoji 連接在一起

### 直接輸出 Emoji 字元

其實每一個 Emoji 就是一個 Unicode 字元，就跟一般文字一樣，所以你大可將 Emoji 字元放在任意字串中，完全沒有問題！

任何支援 Unicode (UTF-8, UTF-16, UTF-32) 的文件，都可以完全合法的放入 Emoji 字元，使用者的作業系統 (包含行動裝置) 只要有安裝含有 Emoji 的字型 (大多已內建)，就可以正確顯示 Emoji 表情圖示。

1.  直接上網搜尋並複製貼上
    
    大部分 Emoji 字元都可以上網找到，直接透過複製/貼上就可以立即使用，相當便利：
    
    -   [📙 Emojipedia — 😃 Home of Emoji Meanings 💁👌🎍😍](https://emojipedia.org/search/)
    -   [Full Emoji List, v11.0](http://www.unicode.org/emoji/charts/full-emoji-list.html)
2.  使用 Windows 10 的 Emoji 輸入法視窗
    
    從 Windows 10 周年更新內建了一個 `Win+.` 快速鍵，可以叫出 Emoji 鍵盤來用，非常方便！
    
    ![Windows 10 的 Emoji 輸入法視窗](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/766523e3335332ad9dcf-50829952-1e34da80-1381-11e9-9be5-daa707eead26.png)
    
3.  透過 HTML Entities 語法輸出到 HTML 文件中
    
    [&what: Discover Unicode & HTML Character Entities](http://www.amp-what.com/unicode/search/)
    
    ```html
    &#x1f534;
    ```
    
4.  透過 JavaScript 的 [Unicode escape sequences](https://mathiasbynens.be/notes/javascript-escapes) 寫在字串中
    
    在寫 JavaScript 字串的時候，直接將 Emoji 字元寫入字串是可以的，例如：
    
    ```javascript
    var a = '💁👌🎍😍';
    ```
    
    JavaScript 有個 [Unicode escape sequences](https://mathiasbynens.be/notes/javascript-escapes) 表示法，可以用在字串中，假設 Unicode 的字碼為 `U+1F197` ( 🆗 )，那麼 JavaScript 有兩種表示法：
    
    1.  ES6 (ES2015) 可以使用 Unicode 的 Code point (碼點) 來表示一個 Unicode 文字 🍔
        
        ```javascript
        var emoji1 = '\u{01F354}';
        var emoji2 = String.fromCodePoint(0x1F354);
        ```
        
        **※ 請注意：即便 ES6 可以用 Unicode 的 Code point (碼點) 來表示一個 Unicode 文字，但事實上內部的文字編碼主要還是以 UTF-16 為主。**
        
    2.  ES5 僅支援 `\uhhhh` 表示法，大部分 Emoji 字元被定義在 BMP 以外的字碼區域，你必須把一個 Unicode 字碼 (Code point) 轉換成兩個 UTF-16 碼元 (code unit) 才行，透過 surrogate pair 的方式來表達一個 Unicode 字元。所以 `U+1F197` 只能用以下語法來寫： 🆗
        
        ```javascript
        '\uD83C\uDD97'
        ```
        
        你可以透過 ES6 (ES2015) 提供的 API 來計算出 ES5 可以使用的兩個 UTF-16 碼元 (code unit)：
        
        ```javascript
        var hb = '\u{01F197}'[0].codePointAt(0).toString(16); // \ud83c
        var lb = '\u{01F197}'[1].codePointAt(0).toString(16); // \udd97
        ```
        
        也可以透過[以下函式](https://web.archive.org/web/20140324172427/http://speakingjs.com/es5/ch24.html)將 UTF-32 字碼轉換為兩個 UTF-16 的表示法：
        
        ```javascript
        function toUTF16(codePoint) {
            var TEN_BITS = parseInt('1111111111', 2);
            function u(codeUnit) {
                return '\\u'+codeUnit.toString(16).toUpperCase();
            }
        
            if (codePoint <= 0xFFFF) {
                return u(codePoint);
            }
            codePoint -= 0x10000;
        
            // Shift right to get to most significant 10 bits
            var leadingSurrogate = 0xD800 | (codePoint >> 10);
        
            // Mask to get least significant 10 bits
            var trailingSurrogate = 0xDC00 | (codePoint & TEN_BITS);
        
            return u(leadingSurrogate) + u(trailingSurrogate);
        }
        ```
        
        使用範例如下：
        
        ```javascript
        toUTF16(0x1F197)
        ```
        

### 將 12 個按鍵符號轉換為 Emoji 圖示

Emoji 定義了 12 個按鍵符號 (`0123456789#*`)，只要加入一組 2 個字元的 Emoji Keycap Sequence (`'\uFE0F\u20E3'`) 就可以把文字自動轉成 Emoji 圖示。

```javascript
var keycap = '\uFE0F\u20E3';
console.assert('0️⃣' === '0' + keycap);
console.assert('1️⃣' === '1' + keycap);
console.assert('2️⃣' === '2' + keycap);
console.assert('3️⃣' === '3' + keycap);
console.assert('4️⃣' === '4' + keycap);
console.assert('5️⃣' === '5' + keycap);
console.assert('6️⃣' === '6' + keycap);
console.assert('7️⃣' === '7' + keycap);
console.assert('8️⃣' === '8' + keycap);
console.assert('9️⃣' === '9' + keycap);
console.assert('#️⃣' === '#' + keycap);
console.assert('*️⃣' === '*' + keycap);
```

### 在現有的 Emoji 字元後方加入菲茨派屈克修飾符 (膚色深度)

在 Emoji 表情圖示的 Unicode 規格中，有個特別的 EMOJI MODIFIER FITZPATRICK (菲茨派屈克修飾符) 可用，這個 **修飾符** (Modifier) 也是一個合法的 Unicode 字元，必須緊接著在 Emoji 字元後面出現。

目前全世界人類的 **膚色分級** ([Fitzpatrick scale](https://en.wikipedia.org/wiki/Fitzpatrick_scale)) 是 1975 年由一位叫做 [Thomas B. Fitzpatrick](https://en.wikipedia.org/wiki/Thomas_B._Fitzpatrick) 的人訂定出來的，他當初定義了 6 種膚色等級。(Type 1 ~ Type 6)。研究 Emoji 竟然還能學習到這種冷知識，哈！

在 Emoji 表情符號中，總共定義了 5 個字元，用來代表 Emoji 的顏色深度。為什麼不是 6 個呢？因為 Type 1 與 Type 2 顏色太相近了，在電腦螢幕上不容易區分，索性就合併了。這五個字元分別是：

| 字元名稱 | Code point | 字元  | UTF-16 (使用 JavaScript 字串) | UTF-8 |
| --- | --- | --- | --- | --- |
| FITZ-1-2 | U+1F3FB | 🏻  | 0xD83C 0xDFFB (`'\uD83C\uDFFB'`) | 0xF0 0x9F 0x8F 0xBB |
| FITZ-3 | U+1F3FC | 🏼  | 0xD83C 0xDFFC (`'\uD83C\uDFFC'`) | 0xF0 0x9F 0x8F 0xBC |
| FITZ-4 | U+1F3FD | 🏽  | 0xD83C 0xDFFD (`'\uD83C\uDFFD'`) | 0xF0 0x9F 0x8F 0xBD |
| FITZ-5 | U+1F3FE | 🏾  | 0xD83C 0xDFFE (`'\uD83C\uDFFE'`) | 0xF0 0x9F 0x8F 0xBE |
| FITZ-6 | U+1F3FF | 🟿  | 0xD83C 0xDFFF (`'\uD83C\uDFFF'`) | 0xF0 0x9F 0x8F 0xBF |

以下是不同的 Emoji 在加上菲茨派屈克修飾符之後的比較表：

| Code point | Default | FITZ-1-2 | FITZ-3 | FITZ-4 | FITZ-5 | FITZ-6 |
| --- | --- | --- | --- | --- | --- | --- |
| U+1F9D2: 小孩 | 🧒  | 🧒🏻 | 🧒🏼 | 🧒🏽 | 🧒🏾 | 🧒🏿 |
| U+1F466: 男孩 | 👦  | 👦🏻 | 👦🏼 | 👦🏽 | 👦🏾 | 👦🏿 |
| U+1F467: 女孩 | 👧  | 👧🏻 | 👧🏼 | 👧🏽 | 👧🏾 | 👧🏿 |
| U+1F9D1: 大人 | 🧑  | 🧑🏻 | 🧑🏼 | 🧑🏽 | 🧑🏾 | 🧑🏿 |
| U+1F468: 男人 | 👨  | 👨🏻 | 👨🏼 | 👨🏽 | 👨🏾 | 👨🏿 |
| U+1F469: 女人 | 👩  | 👩🏻 | 👩🏼 | 👩🏽 | 👩🏾 | 👩🏿 |

我簡單透過 JavaScript 示範將 🧑 (大人) 膚色改成 🧑🏿 (黑人) 的程式寫法：

```javascript
var adult = '🧑';

var fitz6 = '\uD83C\uDFFF';

var black = adult + fitz6; // 只要透過簡單的字串相加就可以完成！

console.log(black);
```

**請注意**：不是所有 Emoji 都能加上膚色，只有跟「人類」有關的 Emoji 才能這樣用，例如 Emoji 中有出現臉、手、腳、身體的，都可以這樣用。目前 Unicode v11.0 定義了 106 個 Emoji 圖示是可以加上 EMOJI MODIFIER FITZPATRICK 字元的，完整清單可以參考[這裡](https://unicode.org/Public/emoji/11.0/emoji-sequences.txt)。

### 透過 **零寬連字(ZWJ)** 將多個 Emoji 連接在一起

這個就真的很酷了，我以前一直很納悶有些 Emoji 到底是怎樣產生的，深入研究後才發現，原來 Emoji 這麼好玩，除了有幾千種不同的 Emoji 之外，還可以將其組合成各種千變萬化的玩法。

Unicode 中有個特殊的 [零寬連字(ZWJ)](https://en.wikipedia.org/wiki/Zero-width_joiner) 符號 (`‍U+200D`)，這基本上是一個合法的 Unicode 文字，只是他預設是看不見的 **零寬**(Zero Width) 字元。這個字元通常用在排版用途，不過卻被 Emoji 拿來用了。你可以透過這個字元，任意串接兩個不同的 Emoji 字元，然後就可以創造出新的、不存在 Emoji 清單中的全新 Emoji 字元。

這邊我做個非常簡單的示範，我直接把 👨 (男人) 與 👩 (女人) 串加起來，但中間卡一個 ZWJ 字元，這個 Emoji 就會自動變成 👨‍👩 (一男一女)：

```javascript
var man = '👨';
var women = '👩';

var zwj = '\u200D';

console.log(man + zwj + women);
```

如果希望顯示一家三口全家福的 Emoji 圖示，可以直接把 👨 (男人) 、 👩 (女人) 與 🧒 (男孩) 串加起來，但不同的字元中間都要卡一個 ZWJ 字元，此時 Emoji 就會自動變成 👨‍👩‍👦 (一男一女外加一個男孩)：

```javascript
var man = '👨';
var women = '👩';
var child = '👦';

var zwj = '\u200D';

console.log(man + zwj + women + zwj + child);
```

相同的邏輯，如果你想顯示一家三口全家福，但小孩是個女兒的話，的 Emoji 圖示，可以直接把 👨 (男人) 、 👩 (女人) 與 👧 (女孩) 串加起來，不同的字元中間依然要卡一個 ZWJ 字元，此時 Emoji 就會自動變成 👨‍👩‍👧 (一男一女外加一個女孩)：

```javascript
var man = '👨';
var women = '👩';
var child = '👧';

var zwj = '\u200D';

console.log(man + zwj + women + zwj + child);
```

如果有兩個小孩呢？沒問題！ `👨 + 👩 + 👦 + 👧 = 👨‍👩‍👦‍👧`

```javascript
var all = ['👨', '👩', '👦', '👧'];
var zwj = '\u200D';
console.log(all.join(zwj));
```

基本上，你要加幾個人都可以！而且在這兩性平權的時代，兩個爸爸、兩個媽媽也不奇怪，想親手試試 Emoji 怎樣設定嗎？

👨‍👨‍👦‍👧

```javascript
var all = ['👨', '👨', '👦', '👧'];
var zwj = '\u200D';
console.log(all.join(zwj));
```

👩‍👩‍👦‍👧

```javascript
var all = ['👩', '👩', '👦', '👧'];
var zwj = '\u200D';
console.log(all.join(zwj));
```

喔！爸爸是巴西人、媽媽是黑人？沒問題！I got you back! 👨🏻‍👩🏿‍👦‍👧

```javascript
var fitz1 = '\uD83C\uDFFB';
var fitz6 = '\uD83C\uDFFF';

var all = ['👨'+fitz1, '👩'+fitz6, '👦', '👧'];
var zwj = '\u200D';
console.log(all.join(zwj));
```

你是一位白人、女性、而且是一位學生？👩🏻‍🎓

```javascript
var fitz1 = '\uD83C\uDFFB';

var all = ['👩'+fitz1, '🎓']; // 加上學士帽 Emoji 即可！
var zwj = '\u200D';
console.log(all.join(zwj));
```

套用 ZWJ 字元的 Emoji 完整清單可以參考[這裡](https://unicode.org/Public/emoji/11.0/emoji-zwj-sequences.txt)。

### 注意事項

雖然 Unicode 明確定義了許多 Emoji 的字碼，但還是有很多平台會自己定義額外的 Emoji 圖示，因此不是所有的 Emoji 大家都看的到，這點必須特別注意。

有些沒有被定義在 Emoji 的 Unicode 符號，在特定作業系統平台也會特別做成**彩色版**的 Emoji 圖示，這部分各位就不用過於擔心，就算在其他平台看不到這個圖示，還是會顯示「文字版」的符號樣式，但也就沒有顏色了。

Unicode 標準定義的 Emoji 清單，可以從 [Full Emoji List, Unicode v11.0](http://www.unicode.org/emoji/charts/full-emoji-list.html) 取得！

### 相關連結

-   [表情圖示 - 維基百科，自由的百科全書](https://zh.wikipedia.org/wiki/%E8%A1%A8%E6%83%85%E5%9B%BE%E6%A0%87)
-   [Full Emoji List, Unicode v11.0](http://www.unicode.org/emoji/charts/full-emoji-list.html)
-   [Fitzpatrick scale - Wikipedia](https://en.wikipedia.org/wiki/Fitzpatrick_scale)
-   [Emojis in Javascript](https://thekevinscott.com/emojis-in-javascript/)
