テック技報最新号へ →
コード

JS: 文字数を数える・切るときは Intl.Segmenter で書記素単位に

str.length や slice() は UTF-16 のコード単位で数えるので、絵文字や結合文字を途中で切ってしまう。見た目の 1 文字(書記素)で扱うなら Intl.Segmenter を使う。

@LiamHammett · 2026-11-10
#javascript#typescript#unicode#frontend

使う場面

「続きを読む…」のプレビューを N 文字で切る、入力欄の文字数カウンタ、ユーザー名の文字数上限チェックなど、ユーザーが見る「文字」の数が大事なところ。

たとえば家族の絵文字 "👨‍👩‍👧" は、人物 3 つと ZWJ(ゼロ幅接合子)2 つの計 5 コードポイントでできていて、.length は 8 になる。[...str].length(コードポイント単位)でも 5 になり、どちらも画面上の 1 文字とは合わない。

やり方

ts
const seg = new Intl.Segmenter("ja", { granularity: "grapheme" });

// 書記素の数
const graphemeLength = (s: string) => [...seg.segment(s)].length;

// 先頭 n 書記素で切る(途中で切れた絵文字が化けない)
function truncate(s: string, n: number, suffix = "…") {
  const parts = [...seg.segment(s)];
  if (parts.length <= n) return s;
  return parts.slice(0, n).map((p) => p.segment).join("") + suffix;
}

"👨‍👩‍👧".length;            // 8
graphemeLength("👨‍👩‍👧");   // 1
truncate("家族👨‍👩‍👧でお出かけ", 3); // "家族👨‍👩‍👧…"
  • granularity は "grapheme"(既定)・"word"・"sentence"。"word" を使うと、スペースで区切らない日本語でも単語に分けられる(isWordLike で記号を除ける)。

注意点

  • DB やバックエンドの文字数上限(バイト数やコードポイント数)とは数え方が違う。フロントの表示用とサーバーの検証用で、どちらの単位なのかを揃えておく。
  • 長い文字列を毎回すべて配列にすると重い。切り出すだけなら for (const { segment } of seg.segment(s)) で n 個に達した時点で抜ける。
  • 主要ブラウザと Node.js 16 以降で使える(Firefox は 125 から)。

出典

X の元の投稿を開く