The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

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

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

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

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

直接輸出 Emoji 字元

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

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

  1. 直接上網搜尋並複製貼上

    大部分 Emoji 字元都可以上網找到,直接透過複製/貼上就可以立即使用,相當便利:

  2. 使用 Windows 10 的 Emoji 輸入法視窗

    從 Windows 10 周年更新內建了一個 Win+. 快速鍵,可以叫出 Emoji 鍵盤來用,非常方便!

    Windows 10 的 Emoji 輸入法視窗

  3. 透過 HTML Entities 語法輸出到 HTML 文件中

    &what: Discover Unicode & HTML Character Entities

    🔴
    
  4. 透過 JavaScript 的 Unicode escape sequences 寫在字串中

    在寫 JavaScript 字串的時候,直接將 Emoji 字元寫入字串是可以的,例如:

    var a = '💁👌🎍😍';
    

    JavaScript 有個 Unicode escape sequences 表示法,可以用在字串中,假設 Unicode 的字碼為 U+1F197 ( 🆗 ),那麼 JavaScript 有兩種表示法:

    1. ES6 (ES2015) 可以使用 Unicode 的 Code point (碼點) 來表示一個 Unicode 文字 🍔

      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 只能用以下語法來寫: 🆗

      '\uD83C\uDD97'
      

      你可以透過 ES6 (ES2015) 提供的 API 來計算出 ES5 可以使用的兩個 UTF-16 碼元 (code unit):

      var hb = '\u{01F197}'[0].codePointAt(0).toString(16); // \ud83c
      var lb = '\u{01F197}'[1].codePointAt(0).toString(16); // \udd97
      

      也可以透過以下函式將 UTF-32 字碼轉換為兩個 UTF-16 的表示法:

      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);
      }
      

      使用範例如下:

      toUTF16(0x1F197)
      

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

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

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) 是 1975 年由一位叫做 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 示範將 🧑 (大人) 膚色改成 🧑🏿 (黑人) 的程式寫法:

var adult = '🧑';

var fitz6 = '\uD83C\uDFFF';

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

console.log(black);

請注意:不是所有 Emoji 都能加上膚色,只有跟「人類」有關的 Emoji 才能這樣用,例如 Emoji 中有出現臉、手、腳、身體的,都可以這樣用。目前 Unicode v11.0 定義了 106 個 Emoji 圖示是可以加上 EMOJI MODIFIER FITZPATRICK 字元的,完整清單可以參考這裡

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

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

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

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

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

var zwj = '\u200D';

console.log(man + zwj + women);

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

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

var zwj = '\u200D';

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

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

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

var zwj = '\u200D';

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

如果有兩個小孩呢?沒問題! 👨 + 👩 + 👦 + 👧 = 👨‍👩‍👦‍👧

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

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

👨‍👨‍👦‍👧

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

👩‍👩‍👦‍👧

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

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

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

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

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

var fitz1 = '\uD83C\uDFFB';

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

套用 ZWJ 字元的 Emoji 完整清單可以參考這裡

注意事項

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

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

Unicode 標準定義的 Emoji 清單,可以從 Full Emoji List, Unicode v11.0 取得!

相關連結

留言評論