絵文字の文字数の数え方 - SNS の文字制限で損しないために
この記事は約 5 分で読めます。
「あれ、絵文字を 5 個入れただけで 30 文字使ってる?」その違和感には、Unicode の構造的な理由があります。絵文字は見た目では 1 文字でも、システム内部では 2〜10 個以上のコードポイントで構成されることが普通です。 プラットフォームごとに「何を 1 文字と数えるか」のルールも異なり、同じ絵文字が Twitter では 2 文字、 別のシステムでは 1 文字として扱われたりします。この記事では、その仕組みと実用的な対処法を解説します。
そもそも「文字」とは何か
絵文字の文字数を語る前に、コンピュータ上の「文字」には複数のレイヤーがあることを押さえる必要があります。
- コードポイント: Unicode が割り当てる番号 1 つ。例: 「あ」は U+3042 で 1 コードポイント。
- コードユニット: 文字エンコーディングが扱う最小単位。UTF-16 では 16 ビット (2 バイト)。
- 書記素クラスタ (grapheme cluster): 人間が「1 文字」と認識する単位。複数のコードポイントから構成されることがある。
絵文字の文字数問題は、この 3 つのレイヤーが食い違うことから生まれます。 プラットフォームによって、どのレイヤーで数えるかが違うのです。
サロゲートペア - 絵文字が 2 文字になる理由
Unicode のコードポイントは U+0000 から U+10FFFF まで定義されており、これを表現するには 21 ビット必要です。 しかし JavaScript の文字列、Java、C# など多くの言語が内部で採用している UTF-16 は、 基本的に 16 ビット (1 コードユニット) で文字を表現する設計です。
16 ビットでは U+FFFF までしか表現できないため、それを超えるコードポイント (絵文字の大半が該当) は、 2 つのコードユニットを組み合わせる「サロゲートペア」で表現します。 つまり、絵文字 1 つを "🌸".length で数えると、JavaScript では 2 が返ります。
"🌸".length // → 2 (サロゲートペア)
"あ".length // → 1 (BMP 範囲内)
[..."🌸"].length // → 1 (コードポイント単位の分解)この差は、Web フォームの文字数バリデーションで「絵文字を入れたら制限を超える」現象の主犯です。 単純に .length で実装している UI は、絵文字利用者を実質的に不利にしています。
ZWJ シーケンス - 1 つの絵文字が 5〜10 コードポイント
家族絵文字や職業絵文字は、複数の絵文字を Zero Width Joiner (U+200D) でつないで 1 つの絵文字に見せる仕組みです。
- 👨👩👧 = 👨 + ZWJ + 👩 + ZWJ + 👧 = 5 コードポイント
- 👨🏽🚀 = 👨 + 肌色 + ZWJ + 🚀 = 4 コードポイント
- 🏳️🌈 = 旗 + 異体字セレクタ + ZWJ + 虹 = 4 コードポイント
UTF-16 でカウントすると、家族絵文字は 11 ユニットを超えることもあります。 見た目は 1 文字、コードポイントでは 5 つ、UTF-16 ユニットでは 10 つ、書記素クラスタでは 1 つ。 どの粒度でカウントされるかで、文字数制限への影響がまったく変わります。
肌色修飾子と異体字セレクタ
👋🏽 のような肌色付きジェスチャーは、ベースの絵文字 + 肌色修飾子の 2 コードポイントで構成されます。 さらに、絵文字としての表示を強制する異体字セレクタ (U+FE0F) が後続するパターンもあります。
- 👋 (素手) = 1 コードポイント
- 👋🏽 (肌色付き) = 2 コードポイント
- ❤️ (赤いハート) = 2 コードポイント (♥ + U+FE0F)
❤️ が「ハート 1 つで 2 文字」になる理由は異体字セレクタです。 テキスト表示の ♥ と区別するため、絵文字としての表示を要求する不可視の制御コードが付随しています。
主要プラットフォームのカウントロジック
X (旧 Twitter) - 重み付きカウント
X は 2018 年から「重み付き文字カウント」を採用しています。 ASCII 文字や一部の通貨記号は 1 文字、それ以外 (日本語・絵文字含む) は 2 文字としてカウント。 日本語ユーザーが 280 文字ではなく実質 140 文字までしか書けないのはこの仕組みが理由です。
絵文字は「見た目 1 つ」を 2 文字とカウントする独自実装で、ZWJ シーケンスでも見た目 1 つなら 2 文字扱い。 サロゲートペア単位ではなく、書記素クラスタ単位で重みを付けている点が特徴です。
Instagram - キャプションと bio で挙動が違う
Instagram のキャプション (2200 文字) は寛容で、絵文字の見た目 1 つをほぼ 1 文字として扱います。 一方で bio (150 文字) はより厳しく、ZWJ シーケンスや特殊な絵文字でカウントが膨らむ場面があります。 bio のデザインに ZWJ 系絵文字を使うと「文字数が足りない」と感じる原因です。
LINE - メッセージは寛容、ステータスは厳しい
LINE のメッセージ本文の上限 (10000 文字) は実質的に絵文字数を気にする必要はありません。 ただしプロフィールのステータスメッセージ (500 文字) は UTF-16 ユニット単位に近いカウントで、 家族絵文字を多用すると思った以上に枠を消費します。
Discord - グレースフル
Discord は内部的にコードポイント単位に近いカウントを採用しており、 絵文字 1 つはほぼ 1 文字として扱われます。bio の文字数制限が緩く感じる理由です。
独自の考察 - なぜプラットフォーム間でカウントが違うのか
プラットフォームのカウントロジックは「ユーザーが直感的に納得するかどうか」と 「内部実装の歴史的経緯」のせめぎあいで決まっています。
書記素クラスタ単位の正確なカウントは、Unicode 標準ライブラリ (ICU) や grapheme-splitter のような 専用ライブラリを必要とします。古くからあるサービスほど内部処理が UTF-16 ベースで作られているため、 後から正確な書記素単位カウントに変えるのはコストが高い。結果として「現実的な妥協」として、 UTF-16 ユニットや独自の重み付けが残っています。
ユーザーから見れば「同じ絵文字が場所によって違う文字数になる」のは不可解ですが、 各プラットフォームの内部事情の現れと捉えると、なぜそうなっているかが見えてきます。
実用的な対処法
1. 重要な投稿は事前にカウントを確認
bio や 1 投稿で勝負するキャプションでは、実際にプラットフォームのフォームに貼り付けて、 残り文字数表示を見るのが確実です。外部の文字数カウンタは多くがコードポイント単位で、 実際のプラットフォームのカウントと一致しないことがあります。
2. 装飾系絵文字は ZWJ を避ける
絵文字アートやプロフィール装飾では、ZWJ シーケンスより単純な絵文字を選んだ方が文字数が抑えられます。 🌸 ☕ 📚 ✨ のような単一コードポイントの絵文字を主役にすると、見た目の華やかさを保ったまま文字数効率が上がります。
3. ❤️ の代わりに ♥
どうしても 1 文字節約したい場面では、❤️ (絵文字 2 コードポイント) ではなくテキストハート ♥ (1 コードポイント) を選ぶ手があります。 ただし、プラットフォームによってはモノクロ表示になるため、見た目とのトレードオフです。
4. 自前で実装するならライブラリを使う
自社サービスでテキストフォームを実装するなら、書記素クラスタ単位でカウントするのが最も誠実です。 JavaScript なら Intl.Segmenter (モダンブラウザ標準) や grapheme-splitter ライブラリが利用できます。 絵文字利用者を不利にしない設計は、現代の Web サービスの基本マナーになりつつあります。
まとめ
絵文字の文字数は、コードポイント・コードユニット・書記素クラスタのどのレイヤーで数えるかで変わります。 プラットフォームごとのロジックを把握し、重要な投稿では実機で残量を確認するのが確実です。 絵文字アートを工夫したいなら、ZWJ を避けて単一コードポイントの絵文字を活かすと文字数効率が上がります。
EmoArt の 探すページ では、シンプルな単一絵文字や装飾文字を組み合わせた combo を多数公開しています。 文字数を抑えながら印象的なプロフィールを作りたい人に役立つはずです。