コンテンツへ移動
ブログに戻る

JavaScriptで絵文字をUnicodeへ変換する

Leibniz Li

@leibnizli
+

絵文字は単純な文字ではありません。Unicodeの**追加面(Supplementary Planes)**に属しています。JavaScriptが絵文字をどのように扱うかを理解することは、文字列の長さ、データベース、クロスプラットフォーム表示を扱う現代のWeb開発で重要です。

問題:.lengthが嘘をつく理由

JavaScriptの文字列はUTF-16でエンコードされます。「A」や「☃」(雪だるま)のような標準文字は基本多言語面(BMP)にあり、16ビットを占有します。一方、😀のような現代の絵文字は追加面にあり、サロゲートペアと呼ばれる2つの16ビットコード単位で表されます。

'A'.length;    // 1 (BMP)
'☃'.length;    // 1 (BMP)
'😀'.length;    // 2 (Supplementary Plane - Surrogate Pair)
'👨‍👩‍👧‍👦'.length; // 11 (Wait, what? See "ZWJ" below)

解決策1:String.prototype.codePointAt()

ES6以前は、サロゲートペアの前半だけを返すcharCodeAt()を使っていました。現在のJavaScriptにはcodePointAt()があり、完全な32ビットUnicode番号を正しく識別できます。

const emoji = '😀';

// ❌ The old way (Incorrect for emojis)
console.log(emoji.charCodeAt(0)); // 55357 (Surrogate lead)

// ✅ The modern way
console.log(emoji.codePointAt(0)); // 128512
console.log(emoji.codePointAt(0).toString(16)); // "1f600"

解決策2:String.fromCodePoint()

Unicode番号を目に見える絵文字へ戻すには、静的メソッドString.fromCodePoint()を使います。

// Decimal
String.fromCodePoint(128512); // "😀"

// Hexadecimal
String.fromCodePoint(0x1F600); // "😀"

// Multiple points
String.fromCodePoint(0x1F1FA, 0x1F1F8); // "🇺🇸"

一括変換:複数の文字

さまざまな絵文字や文字を含む文字列も、まとめて簡単に変換できます。初心者には**スプレッド演算子[...]**がおすすめです。「Unicode対応」なので、絵文字を途中で分割しません。

1.文字列から数値へ(エンコード)

.map()を使って、Unicode番号の配列(10進数または16進数)を取得できます。

const text = "Hi 😀 🚀";

// Convert to Decimal numbers
const decimals = [...text].map(char => char.codePointAt(0));
console.log(decimals); 
// [72, 105, 32, 128512, 32, 128640]

// Convert to Hexadecimal strings (common for CSS/JS escape)
const hexCodes = [...text].map(char => `0x${char.codePointAt(0).toString(16)}`);
console.log(hexCodes);
// ["0x48", "0x69", "0x20", "0x1f600", "0x20", "0x1f680"]

2.数値から文字列へ(デコード)

数値を読みやすい文字列へ戻すには、**レスト演算子...**とString.fromCodePointを組み合わせます。

const myNumbers = [128512, 128640, 9731];

// This "spreads" the array items as individual arguments
const result = String.fromCodePoint(...myNumbers);
console.log(result); // "😀🚀☃"

3.シンプルなループを使う

.map()が分かりにくい場合は、for...ofループでも問題ありません。

const input = "🍎🍊";
for (let char of input) {
  let hex = char.codePointAt(0).toString(16);
  console.log(`Character: ${char} -> Unicode: U+${hex.toUpperCase()}`);
}
// Character: 🍎 -> Unicode: U+1F34E
// Character: 🍊 -> Unicode: U+1F34A

複雑な絵文字:ゼロ幅接合子(ZWJ)

1文字に見える絵文字の中には、**ゼロ幅接合子(ZWJ、\u200D)**という特殊文字で複数のUnicodeポイントを結合したシーケンスがあります。

例えば「家族」の絵文字👨‍👩‍👧‍👦は、次の要素で構成されます。 Man + ZWJ + Woman + ZWJ + Girl + ZWJ + Boy

正しく反復処理するには、Unicode対応のスプレッド演算子[...]またはArray.from()を使います。

const complexEmoji = '👨‍👩‍👧‍👦';

// ❌ Standard split (breaks the emoji)
console.log(complexEmoji.split('')); 
// ["\ud83d", "\udc68", "‍", "\ud83d", ...]

// ✅ Unicode-aware iteration
const points = [...complexEmoji];
console.log(points); 
// ["👨", "‍", "👩", "‍", "👧", "‍", "👦"]

// 1. Get code points as Hexadecimal (commonly used in docs)
const hexCodes = [...complexEmoji].map(c => `0x${c.codePointAt(0).toString(16)}`);
console.log(hexCodes);
// ["0x1f468", "0x200d", "0x1f469", "0x200d", "0x1f467", "0x200d", "0x1f466"]

// 2. Get code points as Decimal (Non-Hexadecimal)
const decimalCodes = [...complexEmoji].map(c => c.codePointAt(0));
console.log(decimalCodes);
// [128104, 8205, 128105, 8205, 128103, 8205, 128102]

// 3. Convert Decimal back to Emoji
console.log(String.fromCodePoint(...decimalCodes)); 
// "👨‍👩‍👧‍👦"

// 4. Convert Hex strings back to Emoji
const emojiFromHex = String.fromCodePoint(...hexCodes);
console.log(emojiFromHex); 
// "👨‍👩‍👧‍👦"

実用例

  1. データベース保存:MySQLなどのデータベースが4バイト文字を保存できるよう、utf8mb4を使用します。
  2. 入力文字数の制限:プロフィールやツイートの文字数を制限する場合は、str.lengthではなく[...str].lengthで絵文字を正しく数えます。
  3. カスタムテキストレンダラー:Canvasベースのゲームや高性能UIに役立ちます。

インタラクティブなツールが必要ですか?

このような変換を頻繁に行う場合は、オンラインUnicode&絵文字コンバーターをお試しください。テキスト、16進数、CSS、JSエスケープシーケンスを双方向に変換できます。

オンラインUnicodeコンバーターを試す →