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 エンコード/デコード
- Unicode エンコード/デコードツールを開く
- テキストまたはコードポイントを入力
- 変換結果を確認:
- テキスト → Unicode エスケープシーケンス(
\u4E2D) - コードポイント → 文字
- UTF-8/16/32 バイトシーケンス
- テキスト → Unicode エスケープシーケンス(
Emoji エンコード
- Emoji エンコードツールを開く
- Emoji を入力
- 完全なエンコード情報を確認:
- コードポイントシーケンス
- 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#字符编码#码点