日本語のフォームでは「全角は2文字、半角は1文字」として文字数を数えたい場面がよくあります。コメント欄の文字数制限や、固定幅での表示そろえ、Shift_JISのバイト数目安などで使われる数え方です。
ポイントは全角かどうかをコードポイントの範囲で正しく判定することです。よくある誤りとして「codePoint >= 0x10000 で判定」がありますが、これはサロゲートペア(絵文字・補助漢字)の判定であって全角判定ではありません(あ・漢字などの全角はBMP内なので検出できません)。正しい実装を見ていきましょう。
for...of でコードポイント単位に走査し、半角(ASCII/Latin-1 と半角カナ)を1、それ以外(全角)を2として数えます。判定には /[\x00-\xFF\uFF61-\uFF9F]/ が便利です。正しいカウント関数
半角の範囲(ASCII〜Latin-1 の U+0000〜U+00FF と半角カナ U+FF61〜U+FF9F)を1、それ以外を全角として2を加算します。for...of はコードポイント単位で回るためサロゲートペアも安全に扱えます。
function getWidth(str) {
let width = 0;
for (const ch of str) { // for...of はコードポイント単位(サロゲートペア安全)
// 半角 = ASCII/Latin-1(U+0000-U+00FF) と 半角カナ(U+FF61-U+FF9F)
width += /[\x00-\xFF\uFF61-\uFF9F]/.test(ch) ? 1 : 2;
}
return width;
}
console.log(getWidth("あいうえおABCDE")); // 15(全角5×2 + 半角5×1)
console.log(getWidth("Hello")); // 5
console.log(getWidth("ハンカク")); // 4(半角カナは1)
str.codePointAt(i) >= 0x10000 で全角を判定するコードは間違いです。0x10000 以上はサロゲートペア(絵文字・補助漢字)であり、あ・漢字・全角英数などの全角文字はBMP内(0x10000 未満)なので検出できません。さらに str.length(UTF-16単位)で回すとサロゲートペアを二重カウントします。正規表現で全角の数から計算する
「各文字を最低1、全角はさらに+1」と考えると、全角の個数を数えるだけでも計算できます。
function countWidth(str) {
// 全角 = 半角の範囲に当てはまらない文字
const fullWidthCount = (str.match(/[^\x00-\xFF\uFF61-\uFF9F]/g) || []).length;
// 体感文字数(コードポイント数)+ 全角の数だけ +1
return [...str].length + fullWidthCount;
}
console.log(countWidth("あいうえおABCDE")); // 15
【重要】「全角2/半角1」はバイト数・文字数とは別物
「文字数を数える」と言っても、目的によって数え方が3通りあり、結果が異なります。混同に注意してください。
const s = "あA"; console.log([...s].length); // 2(体感文字数:全角も半角も1) console.log(getWidth(s)); // 3(全角2/半角1:表示幅) console.log(new TextEncoder().encode(s).length); // 4(UTF-8バイト数:全角3/半角1)
絵文字・サロゲートペアの扱い
for...of はサロゲートペア(絵文字 🎉 など)を1つのコードポイントとして扱うため、上の getWidth では絵文字も全角と同じく2としてカウントされます。(絵文字を1幅・2幅どちらで数えるかは要件次第なので、必要なら個別に分岐してください。)
console.log(getWidth("\u{1F389}A")); // 3(絵文字=2 + 半角A=1)
// もし旧コードのように str.length で回すと…
console.log("\u{1F389}".length); // 2(サロゲートペアで2単位)→ 二重カウントの原因
実用例:全角2字換算の入力カウンタ
「全角200文字(=半角400相当)まで」のような制限を、リアルタイムに表示できます。
const input = document.getElementById("comment");
const counter = document.getElementById("counter");
const MAX = 400; // 半角換算の上限(全角なら200文字)
input.addEventListener("input", () => {
const w = getWidth(input.value);
counter.textContent = `${w} / ${MAX}`;
counter.style.color = w > MAX ? "red" : "#333";
});
数え方の比較
| 数え方 | "あ" |
"A" |
主な用途 |
|---|---|---|---|
体感文字数 [...str].length |
1 | 1 | 見た目の文字数 |
| 全角2/半角1(本記事) | 2 | 1 | 表示幅・Shift_JIS目安・フォーム制限 |
| UTF-8バイト数 | 3 | 1 | 通信量・DB容量(詳細) |
よくある質問(FAQ)
for...of で1文字ずつ走査し、半角(/[\x00-\xFF\uFF61-\uFF9F]/ に一致)なら+1、それ以外(全角)なら+2します。記事内の getWidth() がその実装です。codePoint >= 0x10000 で全角を判定してはいけないのはなぜ?0x10000 以上はサロゲートペア(絵文字・補助漢字)の範囲で、全角判定ではないからです。あ・漢字・全角英数などの全角文字はBMP内(0x10000 未満)にあるため、この条件では検出できず全角が1としてカウントされてしまいます。new TextEncoder().encode(str).length を使います。U+FF61〜U+FF9F)は半角なので1として数えます。記事の判定式はこの範囲を含めているため、getWidth("ハンカク") は4を返します。まとめ
JavaScriptで全角を2・半角を1として数える方法のポイントを整理します。
for...ofでコードポイント単位に走査し、半角=1・全角=2で加算- 半角判定は
/[\x00-\xFF\uFF61-\uFF9F]/(ASCII/Latin-1+半角カナ) codePoint >= 0x10000での全角判定は誤り(それはサロゲートペア判定)- 「全角2/半角1」は表示幅の数え方で、UTF-8バイト数(全角3)・体感文字数(全角1)とは別物
- 絵文字は
for...ofなら全角と同じく2でカウントされる
関連として、文字列のバイト数を取得する方法や全角英数を半角に変換する方法、文字を1文字ずつ取得する方法もあわせて確認すると、文字幅の扱いに強くなれます。

