Unicode と Emoji エンコーディング完全ガイド:コードポイントからレンダリングまで文字コードのすべて

Unicode:すべての文字を統一するエンコーディング標準

Unicode は、世界のほぼすべての文字体系の各文字に一意の番号(コードポイント)を割り当てます。

概念 説明 例
コードポイント (Code Point) 文字の一意な番号 U+4E2D (中)
コードプレーン 65,536 コードポイントごとの平面 BMP(0 番プレーン)
BMP 基本多言語面、U+0000〜U+FFFF ほとんどの常用文字
追加プレーン U+10000〜U+10FFFF Emoji、古文字、稀な文字

一、UTF エンコーディングファミリー

エンコーディング バイト長 "中" のエンコード 特徴
UTF-8 1〜4 バイト E4 B8 AD 可変長、ASCII 互換、Web の第一選択
UTF-16 2 または 4 バイト 4E2D 可変長、JS が内部で使用
UTF-32 固定 4 バイト 00004E2D 固定長、メモリ消費が大きい

UTF-8 エンコーディングルール

コードポイント範囲 バイト数 エンコードテンプレート
U+0000 〜 U+007F 1 0xxxxxxx
U+0080 〜 U+07FF 2 110xxxxx 10xxxxxx
U+0800 〜 U+FFFF 3 1110xxxx 10xxxxxx 10xxxxxx
U+10000 〜 U+10FFFF 4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

エンコード例

文字: 中
コードポイント: U+4E2D = 0100 1110 0010 1101

UTF-8 (3バイト):
  1110xxxx 10xxxxxx 10xxxxxx
  1110 0100 10 111000 10 101101
  = E4 B8 AD

UTF-16 (2バイト):
  4E2D

UTF-32 (4バイト):
  00004E2D

二、JavaScript の Unicode の落とし穴

文字列の長さが正確ではない

'中'.length              // 1 ✅
'𠮷'.length             // 2 ❌ (追加プレーン文字、サロゲートペア)
'👨‍👩‍👧‍👦'.length           // 11 ❌ (Emoji ZWJ シーケンス)
'🏳️‍🌈'.length             // 6 ❌ (旗 Emoji)

原因:JS は UTF-16 を使用

追加プレーンの文字(コードポイント > U+FFFF)は、サロゲートペア(2 つの UTF-16 コードユニット)で表現されます:

文字: 𠮷 (U+20BB7)
UTF-16 サロゲートペア: D842 DF87
JS: length = 2 (2 つのコードユニット)

正しい処理方法

// 方法 1:スプレッド構文
[...'𠮷'].length           // 1 ✅
[...'👨‍👩‍👧‍👦'].length         // 1 ✅ (ただし ZWJ シーケンスは 1 つとしてカウント)

// 方法 2:Array.from
Array.from('𠮷').length    // 1 ✅

// 方法 3:for...of
for (const char of '𠮷👨‍👩‍👧‍👦') {
  console.log(char);  // 文字ごとにイテレート
}

コードポイント操作

// コードポイントを取得
'中'.codePointAt(0)        // 20013 (0x4E2D)
'𠮷'.codePointAt(0)        // 134047 (0x20BB7)

// コードポイントから文字を生成
String.fromCodePoint(0x4E2D)  // '中'
String.fromCodePoint(0x20BB7) // '𠮷'

// 注意:charCodeAt は追加プレーンを扱えない
'𠮷'.charCodeAt(0)         // 55362 (最初のサロゲートのみ)

三、Emoji エンコーディングの詳細解析

Emoji の 4 つのタイプ

タイプ 例 コードポイント 長さ
基本 Emoji 😀 U+1F600 1 (コードポイント)
修飾子 Emoji 👩🏽 U+1F469 + U+1F3FD 2 (コードポイント)
ZWJ シーケンス 👨‍👩‍👧‍👦 複数コードポイント + ZWJ 7 (コードポイント)
旗 Emoji 🇨🇳 U+1F1E8 + U+1F1F3 2 (コードポイント)

ゼロ幅接合子 (ZWJ)

ZWJ (U+200D) は複数の Emoji を 1 つの複合 Emoji に結合します:

👨 (U+1F468) + ZWJ (U+200D) + 👩 (U+1F469) + ZWJ (U+200D) + 👧 (U+1F467) + ZWJ (U+200D) + 👦 (U+1F466)
= 👨‍👩‍👧‍👦 (4 人家族)

肌の色修飾子

肌の色修飾子 (U+1F3FB 〜 U+1F3FF) は Emoji の肌色を変更します:

👩 (U+1F469) + 🏽 (U+1F3FD) = 👩🏽 (中程度の肌色の女性)
修飾子 コードポイント 肌の色
🏻 U+1F3FB 明るい
🏼 U+1F3FC やや明るい
🏽 U+1F3FD 中程度
🏾 U+1F3FE やや暗い
🏿 U+1F3FF 暗い

旗 Emoji

旗は 2 つの地域指標記号で構成されます(ISO 3166-1 alpha-2):

🇨 (U+1F1E8) + 🇳 (U+1F1F3) = 🇨🇳 (中国)
🇺 (U+1F1FA) + 🇸 (U+1F1F8) = 🇺🇸 (アメリカ)
🇯 (U+1F1EF) + 🇵 (U+1F1F5) = 🇯🇵 (日本)

四、異体字セレクター

VS16 (U+FE0F) はテキスト表示の文字を Emoji 表示に切り替えます:

❤ (U+2764)           → テキスト表示:❤ (黒いハート)
❤️ (U+2764 + U+FE0F) → Emoji 表示:❤️ (赤いハート)
文字 VS16 なし VS16 あり
☕ ☕ (テキスト) ☕️ (Emoji)
⚡ ⚡ (テキスト) ⚡️ (Emoji)
❤ ❤ (テキスト) ❤️ (Emoji)

五、ツールボックスで Unicode を扱う

Unicode エンコード/デコード

  1. Unicode エンコード/デコードツールを開く
  2. テキストまたはコードポイントを入力
  3. 変換結果を確認:
    • テキスト → Unicode エスケープシーケンス(\u4E2D)
    • コードポイント → 文字
    • UTF-8/16/32 バイトシーケンス

Emoji エンコード

  1. Emoji エンコードツールを開く
  2. Emoji を入力
  3. 完全なエンコード情報を確認:
    • コードポイントシーケンス
    • UTF-8 バイト
    • ZWJ/修飾子の分解
    • CLDR ショートネーム

六、よくある落とし穴と解決策

落とし穴 1:文字列の切り詰め

// 誤り:切り詰めがサロゲートペアを分断する可能性
text.substring(0, 10)  // 無効な文字が生じることがある

// 正解:コードポイント単位で切り詰め
function sliceByCodePoint(str, start, end) {
  const chars = [...str];
  return chars.slice(start, end).join('');
}

落とし穴 2:文字列の反転

// 誤り:反転が結合文字を破壊する
'👨‍👩‍👧‍👦'.split('').reverse().join('')  // 文字化け

// 正解:コードポイント単位で反転
[...'👨‍👩‍👧‍👦'].reverse().join('')  // ✅

落とし穴 3:正規表現マッチ

// 誤り:. は追加プレーン文字にマッチしない
/^.$/.test('𠮷')  // false

// 正解:u フラグを使う
/^.$/u.test('𠮷')  // true

落とし穴 4:ソート

// デフォルトのソートは UTF-16 コードユニット順
['中', '𠮷', 'a'].sort()  // 期待通りにならない場合がある

// 正解:コードポイントでソート
['中', '𠮷', 'a'].sort((a, b) =>
  a.codePointAt(0) - b.codePointAt(0)
)

七、便利な API

// 正規化(結合文字の等価性を処理)
'café'.normalize('NFC')   // café (合成)
'café'.normalize('NFD')   // café (分解)

// Emoji の検出
function isEmoji(char) {
  const code = char.codePointAt(0);
  return (code >= 0x1F600 && code <= 0x1F64F) ||  // Emoticons
         (code >= 0x1F300 && code <= 0x1F5FF) ||  // Misc Symbols
         (code >= 0x1F680 && code <= 0x1F6FF) ||  // Transport
         (code >= 0x1F900 && code <= 0x1F9FF);    // Supplemental
}

// 文字列の実際の長さ(書記素単位)を取得
function graphemeLength(str) {
  const segmenter = new Intl.Segmenter('zh', { granularity: 'grapheme' });
  return [...segmenter.segment(str)].length;
}

graphemeLength('👨‍👩‍👧‍👦')  // 1 ✅
graphemeLength('🏳️‍🌈')    // 1 ✅
#Unicode#Emoji#UTF-8#字符编码#码点