JavaScriptで文字数を正しく数えるには?Unicode・絵文字・LinkedIn投稿ツールを例に考えてみた
LinkedInやWhatsApp向けの文章を整えるツールを作っていると、単純に見える文字列処理でも、意外と考えることが多いと感じます。
例えば、JavaScriptで文字数を数えるなら、まず思いつくのは "text.length" ではないでしょうか。
const text = "Hello World";
console.log(text.length); // 11
普通の英数字なら、これで特に問題はありません。
ところが、絵文字やUnicodeの特殊文字が入ると、少し事情が変わります。
今回は、私が開発している無料Webツール Sarticles を例にしながら、JavaScriptの文字数カウント、Unicode文字、LinkedIn向けのテキスト整形について整理してみます。
この記事では、特定のサービスの内部実装を紹介するのではなく、同じようなツールを作るときに役立つJavaScriptの基本的な考え方を紹介します。
- JavaScriptの文字数カウントで注意したいこと
まず、以下のコードを見てください。
const text = "Hello 😊";
console.log(text.length); // 8
console.log([...text].length); // 7
見た目は7文字なのに、"length" は8を返しています。
これは、JavaScriptの文字列がUTF-16のコードユニットを基準に扱われるためです。
一部の絵文字はUTF-16で2つのコードユニットを使用するので、"length" の結果と人間が見た文字数が一致しません。
"[...text]" を使用するとコードポイント単位で数えられますが、これでもすべてのケースに対応できるわけではありません。
例えば、複数の絵文字を組み合わせた家族の絵文字などは、見た目が1つでも内部的には複数のコードポイントで構成されています。
Intl.Segmenterを使う方法
ユーザーが見た目として認識する文字に近い単位で数える場合は、"Intl.Segmenter" が役立ちます。
function countCharacters(text) {
const segmenter = new Intl.Segmenter("en", {
granularity: "grapheme"
});
return [...segmenter.segment(text)].length;
}
console.log(countCharacters("Hello 😊")); // 7
console.log(countCharacters("👨👩👧👦")); // 1
この方法では、grapheme cluster(書記素クラスタ)という単位で文字列を分割できます。
ただし、ここで注意したいのは、ユーザーから見た文字数と、SNS側が内部的に採用する文字数制限の計算方法が必ずしも同じではないという点です。
SNS向けの文字数カウンターを作る場合は、Unicodeの処理に加えて、対象プラットフォームの仕様も確認する必要があります。
- LinkedInの太字はHTMLの太字とは違う
LinkedInの投稿を作成するとき、特定の単語を太字のように見せたい場面があります。
例えば、
Important Update
という文章を、次のように表示したいケースです。
𝗜𝗺𝗽𝗼𝗿𝘁𝗮𝗻𝘁 𝗨𝗽𝗱𝗮𝘁𝗲
ここで使っているのは、HTMLの "" タグではありません。
通常のアルファベットとは別に定義された、Unicodeの数学用英数字記号です。
この違いは、テキスト整形ツールを作るうえで重要になります。
JavaScriptで簡単な変換を試す
以下は、英語の大文字・小文字をUnicodeの太字に変換するサンプルです。
function toUnicodeBold(text) {
return Array.from(text, char => {
const code = char.codePointAt(0);
if (code >= 65 && code <= 90) {
return String.fromCodePoint(
0x1D5D4 + code - 65
);
}
if (code >= 97 && code <= 122) {
return String.fromCodePoint(
0x1D5EE + code - 97
);
}
return char;
}).join("");
}
console.log(toUnicodeBold("Hello LinkedIn"));
実行結果:
𝗛𝗲𝗹𝗹𝗼 𝗟𝗶𝗻𝗸𝗲𝗱𝗜𝗻
このコードは英語の基本的なアルファベットを対象にしたシンプルな例です。数字、日本語、その他の文字種は変換対象に含めていません。
実際のツールでは、変換できない文字をどう扱うか、元の文章をどう保持するかといった点も考える必要があります。
また、Unicodeによる装飾文字は、スクリーンリーダーでの読み上げや検索、コピー後の処理に影響する場合があります。
そのため、長い文章全体を装飾するより、見出しや重要な単語などに限定して使うほうが適切です。
- このような問題を試せるSarticlesというツール
私は現在、"Sarticles" (https://sarticles.in/) という無料のWebツールサイトを運営しています。
LinkedInやWhatsAppで文章を書く際の、小さな不便を解消することが目的です。
現在は、主に次の4つのツールを公開しています。
LinkedIn Text Formatter
"LinkedIn Text Formatter" (https://sarticles.in/tools/linkedin-text-formatter/)
通常のテキストをUnicodeの太字、斜体、その他のスタイルに変換できるツールです。
例えば、先ほど紹介したUnicode文字の違いを、ブラウザ上で試すことができます。
LinkedIn Character Counter
"LinkedIn Character Counter" (https://sarticles.in/tools/linkedin-character-counter/)
LinkedIn向けの文章について、文字数、単語数、行数、ハッシュタグなどを確認できるツールです。
Unicodeを意識したカウント機能も用意しています。
単純な "text.length" と、ユーザーが見た文字数が異なるケースを考えるきっかけにもなります。
ただし、LinkedInが実際に適用する文字数制限については、最終的に投稿画面でも確認することをおすすめします。
LinkedIn Post Formatter
"LinkedIn Post Formatter" (https://sarticles.in/tools/linkedin-post-formatter/)
LinkedIn投稿の改行、箇条書き、文字装飾、プレビューなどを確認するためのツールです。
特に、投稿前に文章の見た目を確認したい場合に便利です。
WhatsApp Text Formatter
"WhatsApp Text Formatter" (https://sarticles.in/tools/whatsapp-text-formatter/)
WhatsApp向けの太字、斜体、取り消し線、引用、リストなどの書式を整えるためのツールです。
LinkedInのUnicode装飾とは異なり、WhatsAppには独自のテキスト書式ルールがあります。
複数のプラットフォームに対応するツールを作るときは、こうした違いを区別する必要があります。
- 文字数だけではなく、編集体験も重要
文字列処理を考えるとき、もう一つ気をつけたいのが入力欄の操作性です。
例えば、ユーザーが文章の途中を選択して太字にしたとき、カーソルが文章の最後に移動してしまうと不便です。
編集内容が正しくても、毎回カーソル位置が変われば使いにくいツールになってしまいます。
通常の "textarea" であれば、次のプロパティを使って選択範囲を取得できます。
const textarea = document.querySelector("textarea");
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
console.log({ start, end });
文字列を更新したあと、必要に応じて "setSelectionRange()" で選択範囲を復元できます。
ただし、ここでもUTF-16に注意が必要です。
"selectionStart" と "selectionEnd" が扱う位置はUTF-16コードユニット単位です。"Intl.Segmenter" の書記素数をそのまま選択位置として使用すると、絵文字などを含む文章でずれる可能性があります。
Unicodeに対応した編集機能を作るときは、文字の数え方だけでなく、文字列内の位置の扱い方も重要になります。
- Webツールを作るときに意識していること
小さなテキストツールでも、実際に使いやすくするためにはいくつかの観点があります。
- 正確性: Unicodeや絵文字を含む入力を正しく扱えているか
- 操作性: 選択範囲、カーソル位置、スクロール位置が意図せず変わらないか
- アクセシビリティ: 装飾された文字がすべてのユーザーに読みやすいとは限らないこと
- 互換性: コピーした文字が別のサービスでも期待どおりに扱われるか
- プライバシー: 入力内容をどのように処理し、保存するかを明確にすること
私がSarticlesで目指しているのは、複雑な機能を増やすことよりも、こうした基本的な使い勝手を改善することです。
まとめ
JavaScriptで文字数を数えたり、Unicode文字を変換したりする処理は、最初は簡単に見えます。
しかし、実際にはUTF-16、コードポイント、書記素クラスタ、アクセシビリティ、各サービスの文字数制限など、考えるべきことがいくつもあります。
今回のポイントは次の3つです。
- "string.length" は必ずしも人間が認識する文字数ではない。
- "Intl.Segmenter" は絵文字を含む文字列を扱う際に役立つが、SNS独自の文字数制限と同じとは限らない。
- LinkedIn向けのUnicode装飾はHTMLの太字とは異なり、互換性やアクセシビリティも考慮する必要がある。
実際の表示や操作を試したい方は、"Sarticlesの無料ツール一覧" (https://sarticles.in/tools/) も参考にしてみてください。
同じようにブラウザベースの文章編集ツールを作っている方にとって、この記事が何かのヒントになればうれしいです。
参考資料
- "MDN — Intl.Segmenter" (https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter)
- "MDN — String.length" (https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/length)
- "MDN — String.codePointAt()" (https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String/codePointAt)
- "Sarticles — Free Online Text Tools" (https://sarticles.in/tools/)