本文へスキップ

文字数カウンタ

テキストエリアの残り文字数をリアルタイム表示。上限に近づくと色が変わり、超過を防ぎやすい。

プレビュー

入力

操作実際に入力して試せます。

使いどころ

入力量に上限があり、ユーザー自身に残り文字数を意識しながら書いてほしい場面に向く。コメント欄、SNS投稿、レビュー入力のように、上限に近づくにつれて調整が必要になる入力で効果が大きい。本実装はmaxlength属性も併用しているため、JSが何らかの理由で失敗してもブラウザ側の入力制限自体は生きる二重構えになっている点は評価できる。逆に、上限が無い自由記述欄や、上限があっても数千文字単位のように余裕が大きい入力には不要で、常時表示されるカウンタがかえって視線のノイズになる。また、日本語入力のように変換確定前の文字(IME入力中)は多くのブラウザでinputイベントのvalueに未確定文字を含む場合と含まない場合が分かれるため、厳密なリアルタイム性を求める用途では入力方式による誤差が起こりうる点を把握しておくべきである。

仕組み

inputイベントのたびにMAX(100)からarea.value.lengthを引いた残り文字数を計算し、cc-counterの中身をcreateElement/appendChildで組み立て直している。innerHTMLではなくDOM APIで組み立てているのは、ユーザー入力値を直接文字列結合しないことでXSSのリスクを避ける安全な書き方になっている。残り文字数に応じてcc-counterのclassNameを"cc-counter"→"cc-counter warn"(残り30以下)→"cc-counter danger"(残り10以下)の3段階で切り替え、CSS側の色だけで警告度を表現する。maxlength="100"がtextarea自身に設定されているため、ブラウザは既にそれ以上の入力を物理的にブロックしており、JS側のカウンタ表示はあくまで補助的な可視化であって、文字数を実際に制限しているのはネイティブのmaxlength属性である点が設計上のポイントになる。

実装の注意

残り文字数はJavaScriptの文字列lengthで数えているため、サロゲートペア(絵文字や一部の異体字)は1文字が2としてカウントされ、見た目の1文字に対して残り数が2つ減る不一致が起きる。Array.from(area.value).lengthに置き換えればサロゲートペアは1と数えられるが、これはコードポイント数であって、結合文字や家族絵文字・国旗のようにコードポイントが複数連なる1文字はまだ複数と数えてしまう。見た目の1文字(書記素)で数えたいなら Intl.Segmenter の granularity: "grapheme" で分割して数えるのが正確だ。次に、cc-counterのDOMをinput毎に毎回作り直す実装(createElement→appendChild)は、100文字程度の短い入力では問題にならないが、上限を大きくして頻繁なinputイベントで使う場合はtextContentの直接更新の方が軽い。また、maxlengthによるブラウザ側の制限とJS側の残り数表示は別々のロジックなので、将来どちらかだけ上限値(MAX定数とmaxlength属性)を変更すると数値が食い違う。2箇所の100という値は同期して変更する必要がある。

アクセシビリティ

cc-counterにはaria-liveもrole属性も付いておらず、残り文字数の変化はスクリーンリーダー利用者に自動的には伝わらない。視覚的に色(warn/danger)だけで警告度を示している点も、色を認識しにくいユーザーには伝わりにくい。改善するには、①cc-counterにaria-live="polite"を付けて変化を読み上げ可能にする(ただし入力の度に読み上げると煩雑なので、warn/dangerの閾値を跨いだ瞬間だけ読ませる、あるいはblur時のみ読ませる工夫が要る)、②textareaにaria-describedby="cc-counter"を付けて、フォーカス時にカウンタの内容を残り文字数の説明として関連付ける、③色の変化に加えて"残り◯文字"というテキスト自体は既に含まれているため、色以外の手がかりは最低限確保できている、の3点を補う必要がある。maxlengthはネイティブ属性なのでブラウザの入力ブロック自体はスクリーンリーダーにも一貫して効く。

調整して使う

調整しやすいのは3点。まず警告の閾値で、既定は残り30(warn)・10(danger)だが、上限が100より大きい場合は割合(例えば残り20%・5%)で計算する方が、上限を変えても警告のタイミングの体感が揃う。次に文言の向きで、現状は「残り◯文字」という減算型の表示だが、投稿系のUIでは「現在の文字数/上限」という表記(例: 42/100)の方が全体量を把握しやすいユーザーもいるため、用途に応じて切り替えられる。最後に、上限を超える直前ではなく超えた場合の挙動で、この実装はmaxlengthにより物理的に超過を防いでいるが、貼り付け(ペースト)で一時的に超過した値が入り、ブラウザによっては即座に切り詰められず一瞬超過状態を表示することがある。厳密に扱うならpasteイベントでも明示的にsliceする防御を足すとよい。

コード(コピーして使えます)

<div class="cc-wrap">
  <label class="cc-label" for="cc-area">コメント</label>
  <textarea
    id="cc-area"
    class="cc-area"
    maxlength="100"
    placeholder="100文字まで入力できます"
    rows="4"
  ></textarea>
  <div class="cc-footer">
    <span class="cc-counter" id="cc-counter">残り <strong>100</strong> 文字</span>
  </div>
</div>

タグ

同じカテゴリ「フォーム入力」のほかのパーツ

関連パーツ