作成日:2025/9/24,最終更新日:2025/9/26

JavaScript / TypeScriptを使用したコーディング例について②

これまで紹介した、記事を活用したコーディング例の紹介

この記事では、dom操作やイベント処理など、JavaScript / TypeScriptと紹介した記事の内容を活用したコーディング例を紹介します。
今回は、文字数カウントを実装する例を紹介します。


1. 導入+基本編

解説: Webフォームでは「入力できる文字数の制限」を設けることがよくあります。
例えば Twitter のように「140文字まで」といった制限がある場合、ユーザーが入力しながら残り文字数を確認できるUIは必須です。
このセクションでは、リアルタイム文字数カウントを最小限のコードで実装し、その仕組みを理解します。




● 基本構造(HTMLとJavaScript)

textarea に入力された文字数を取得し、上限値(ここでは100文字)から残数を計算して表示します。

<!-- HTML: 入力欄と残り文字数表示 -->
<textarea id="message" rows="4" cols="40" placeholder="メッセージを入力してください"></textarea>
<div>残り文字数: <span id="remaining">100</span></div>

<!-- JavaScript -->
<script>
  const textarea = document.getElementById('message');
  const remaining = document.getElementById('remaining');
  const MAX_LENGTH = 100;

  function updateCount() {
    const currentLength = textarea.value.length;
    remaining.textContent = MAX_LENGTH - currentLength;
  }

  textarea.addEventListener('input', updateCount);

  // 初期表示
  updateCount();
</script>

● 実行例(デモ)※制限文字数少なくしています。

下の入力欄に文字を入力して、残り文字数が即座に変化する様子を確認してください。


残り文字数: 20

● ポイント整理

  • value.length で入力文字数を取得できる
  • ・残数は「最大値 − 現在の文字数」で計算
  • inputイベントを使えば、キー入力・貼り付け・削除などすべての操作に即時対応できる

2. 応用:上限超過のUI制御

解説: 文字数カウントの基本形に、上限超過時のフィードバックを追加します。
残数がマイナスになったら赤く表示し、エラーメッセージを出したり、送信ボタンを無効化することで、 ユーザーが制限を超えて入力できないようにする工夫が重要です。




● 基本構造(HTMLとJavaScript)

上限を 100 文字に設定し、残数が負になったらエラーUIを表示します。

<!-- HTML -->
<form id="msgForm">
  <textarea id="message" rows="4" cols="40" placeholder="メッセージを入力してください"></textarea>
  <div>
    残り文字数: <span id="remaining">100</span>
    <span id="errorMsg" style="color:red; margin-left:.5rem; display:none;">文字数オーバーです!</span>
  </div>
  <button id="submitBtn" type="submit">送信</button>
</form>

<!-- JavaScript -->
<script>
  const textarea = document.getElementById('message');
  const remaining = document.getElementById('remaining');
  const errorMsg = document.getElementById('errorMsg');
  const submitBtn = document.getElementById('submitBtn');
  const MAX_LENGTH = 100;

  function updateCount() {
    const currentLength = textarea.value.length;
    const rest = MAX_LENGTH - currentLength;
    remaining.textContent = rest;

    if (rest < 0) {
      remaining.style.color = 'red';
      errorMsg.style.display = 'inline';
      submitBtn.disabled = true;
    } else {
      remaining.style.color = 'black';
      errorMsg.style.display = 'none';
      submitBtn.disabled = false;
    }
  }

  textarea.addEventListener('input', updateCount);

  // submit時の再検証
  document.getElementById('msgForm').addEventListener('submit', (e) => {
    if (textarea.value.length > MAX_LENGTH) {
      e.preventDefault();
      alert('文字数が多すぎます。修正してください。');
    }
  });

  updateCount();
</script>

● 実行例(デモ)

下のテキストエリアに文字を入力してください。
制限(20字)を超えると残数が赤くなり、送信ボタンが無効化されます。

残り文字数: 100

● まとめ

  • ・残数が負になったら色やメッセージで即時フィードバック
  • 送信ボタンを無効化し、制限を超えた送信を防ぐ
  • submit時の再検証も忘れずに実装する

3. アクセシビリティ対応

解説: 文字数カウントを実装する際には、見た目だけでなくアクセシビリティにも配慮する必要があります。
特に、スクリーンリーダー利用者や色覚に個性のあるユーザーにとっては、「残り文字数を音声で読み上げる」「赤色だけに頼らない通知」が重要です。
ここでは、aria-live 属性や aria-invalid を活用した、アクセシブルな文字数カウントを作ってみましょう。




● 基本構造(HTML + CSS + JavaScript)

以下のポイントを実装します:
・残り文字数を aria-live="polite" で自動読み上げ
・上限超過時は aria-invalid="true" を付与し、エラーメッセージを role="alert" で即時通知
・赤色表示に加えて明示的なテキストで伝達
・送信ボタンを自動で無効化して二重にガード

<!-- HTML -->
<form id="msgFormDemo3">
  <label for="messageDemo3">メッセージ(100文字まで)</label>
  <textarea
    id="messageDemo3"
    rows="4"
    cols="40"
    aria-describedby="charDescDemo3 charCountDemo3"
    aria-live="off"
    aria-invalid="false"
    placeholder="メッセージを入力してください"></textarea>

  <div id="charDescDemo3" class="visually-hidden">
    入力可能な残り文字数が自動で更新されます。
  </div>

  <div id="charCountDemo3" aria-live="polite" role="status">残り 100 文字</div>
  <div id="errorWrapDemo3" class="visually-hidden" role="alert"></div>

  <button id="submitBtnDemo3" type="submit">送信</button>
</form>

<!-- CSS -->
<style>
  .visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden; clip: rect(0 0 0 0);
    border: 0; white-space: nowrap;
  }
  #messageDemo3 {
    width: 100%;
    padding: .5rem;
    font-size: 1rem;
    border: 1px solid #ccc;
    border-radius: 4px;
  }
  #messageDemo3.error {
    border-color: #d00;
    outline: 2px solid #f3bcbc;
  }
  #charCountDemo3 {
    margin-top: .25rem;
    font-weight: bold;
  }
  #charCountDemo3.over {
    color: #d00;
  }
  #submitBtnDemo3 {
    margin-top: .5rem;
    padding: .5rem 1rem;
    background: #87bfff;
    color: #fff;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }
  #submitBtnDemo3:disabled {
    background: #ccc;
    cursor: not-allowed;
  }
</style>

<!-- JavaScript -->
<script>(function(){
  const MAX_DEMO3 = 100;
  const textarea  = document.getElementById('messageDemo3');
  const counter   = document.getElementById('charCountDemo3');
  const alertBox  = document.getElementById('errorWrapDemo3');
  const submitBtn = document.getElementById('submitBtnDemo3');

  function updateCountDemo3(){
    const len  = textarea.value.length;
    const rest = MAX_DEMO3 - len;

    counter.textContent = `残り ${rest} 文字`;
    const over = rest < 0;

    counter.classList.toggle('over', over);
    textarea.classList.toggle('error', over);
    textarea.setAttribute('aria-invalid', over ? 'true' : 'false');
    submitBtn.disabled = over;

    // 上限超過時は alert ロールで即時通知
    alertBox.textContent = over ? '上限を超えています。文字数を減らしてください。' : '';
  }

  textarea.addEventListener('input', updateCountDemo3);

  document.getElementById('msgFormDemo3').addEventListener('submit', function(e){
    if (textarea.value.length > MAX_DEMO3) {
      e.preventDefault();
      textarea.focus(); // 修正を促す
    }
  });

  updateCountDemo3();
})();</script>

● 実行例(デモ)※制限文字数少なくしています。


残り 100 文字

● まとめ

  • aria-live="polite":残り文字数など、頻繁に変わる情報を数秒遅れで読み上げ(邪魔にならない通知)
  • role="alert" / aria-live="assertive":エラーや警告を即時読み上げ(緊急度の高い通知)
  • aria-invalid:入力欄がエラー状態であることをマークし、後からフォーカスしたときも「エラー」と認識される
  • ・赤色だけでなくテキスト通知を併用することで、色覚多様性に配慮
  • ・送信ボタンを自動無効化し、送信時も二重にガード
  • ・実務で使うなら、この仕組みを標準パターンとして導入すると安心

4. 実務Tips①:数え方の落とし穴

解説: 一見シンプルに見える「文字数カウント」ですが、実務では「何を1文字と数えるか」が意外と難しいポイントです。
JavaScriptの string.length は「UTF-16コード単位の数」を返すため、絵文字やサロゲートペアを含むとズレが発生します。
また、X(旧Twitter)のようにURLを固定長(23文字)で換算する仕様もあります。
ここでは、代表的な落とし穴と対策を紹介します。




● string.length の落とし穴

JavaScript標準の length はサロゲートペア(例:😊)を2文字として数えます。
そのため、画面に「1文字」と見えても、実際には「2」とカウントされてしまいます。

<!-- 絵文字を含む場合のカウント比較 -->
<div id="resultDemo4a"></div>

<script>
(function(){
  const text = "ABC😊DEF";
  const len = text.length; // UTF-16コード単位 → 8
  const trueLen = [...text].length; // スプレッドでコードポイント数 → 7

  document.getElementById("resultDemo4a").textContent =
    `文字列: ${text} / length: ${len} / コードポイント数: ${trueLen}`;
})();
</script>

● URLを含む場合の特殊ルール

X(旧Twitter)などでは、URLは1本あたり23文字とみなす独自ルールがあります。
文字数制限のあるSNS投稿をシミュレーションするなら、このルールを取り込む必要があります。

<!-- URLを固定長換算する例 -->
<div id="resultDemo4b"></div>

<script>
(function(){
  const text = "公式サイトはこちら → https://example.com";
  const urlRegex = /https?:\/\/\S+/g;
  let adjusted = text.replace(urlRegex, "X".repeat(23)); // URLを23文字に換算
  const count = [...adjusted].length;

  document.getElementById("resultDemo4b").textContent =
    `元の文字列: ${text} / 換算後の文字数: ${count}`;
})();
</script>

● 実行例(デモ)

length(UTF-16単位): 0
コードポイント数: 0
URL換算(23文字固定): 0

● まとめ

  • string.length は UTF-16単位 → 絵文字や特殊文字でズレやすい
  • [...text].length でコードポイント数を取れば「見た目の1文字」に近いカウントが可能
  • URL換算 のようにサービス固有のルールが存在する場合もある
  • ・実務では「何を1文字とみなすか」を仕様として明確化しておくのが重要

5. 応用:複数フィールドやSNS風カウント

解説: 実務では「1つの入力欄の文字数」だけではなく、複数のフィールドを合計して制御するケースがあります。
例えばSNS投稿のように、本文・ハッシュタグ・URLを合わせて「合計280文字まで」といった制約です。
この場合、各入力欄の長さを集計し、合計で上限を超えないようにチェックする必要があります。




● 基本構造

下記の例では「本文+ハッシュタグ+URL」を合計し、280文字制限をかけています。
URLはX(旧Twitter)風に23文字固定として換算します。

<!-- 複数フィールドを合計して制御 -->
<form id="snsForm">
  <textarea id="postBody" rows="3" cols="40" placeholder="本文を入力"></textarea><br>
  <input id="postTags" type="text" placeholder="#ハッシュタグ"><br>
  <input id="postUrl" type="text" placeholder="URL(任意)"><br>
  <div>残り文字数: <span id="remainingSNS">280</span></div>
  <button id="submitBtnSNS" type="submit">投稿</button>
</form>

<script>
(function(){
  const MAX = 280;
  const body = document.getElementById("postBody");
  const tags = document.getElementById("postTags");
  const url  = document.getElementById("postUrl");
  const remaining = document.getElementById("remainingSNS");
  const submitBtn = document.getElementById("submitBtnSNS");

  function getCount(){
    let total = [...body.value].length + [...tags.value].length;
    if(url.value.trim() !== ""){
      total += 23; // URLは1本でも入力があれば固定23文字としてカウント
    }
    return total;
  }

  function update(){
    const used = getCount();
    const rest = MAX - used;
    remaining.textContent = rest;

    if(rest < 0){
      remaining.style.color = "red";
      submitBtn.disabled = true;
    }else{
      remaining.style.color = "black";
      submitBtn.disabled = false;
    }
  }

  body.addEventListener("input", update);
  tags.addEventListener("input", update);
  url.addEventListener("input", update);
  update();
})();
</script>

● 実行例(デモ)




残り文字数: 280

● まとめ

  • ・本文・ハッシュタグ・URLなど、複数フィールド合計で制御することがある
  • ・URLは1本あたり23文字として換算するなど、サービス固有のルールに合わせる必要がある
  • ・残数表示やボタン制御は一括で管理するのが実務的
  • ・この仕組みを使えばSNS投稿風のUIを簡単に再現できる

6. 保存と復元(localStorage活用)

解説: 入力途中でブラウザを閉じても、localStorageを使えば再度開いたときに内容を復元できます。
特に長文フォームでは「途中で消えない安心感」があり、ユーザー体験を大きく向上させます。
ここでは、入力内容と文字数カウントを自動で保存・復元する仕組みを実装してみましょう。




● 基本構造

下記の例では、テキストエリアの内容を入力ごとにlocalStorageへ保存し、ページ読み込み時に復元します。

<!-- 保存と復元の仕組み -->
<form id="storageForm">
  <textarea id="storageMessage" rows="4" cols="40" placeholder="メッセージを入力してください"></textarea>
  <div>残り文字数: <span id="remainingStorage">100</span></div>
  <button type="submit">送信</button>
</form>

<script>
(function(){
  const MAX = 100;
  const textarea = document.getElementById("storageMessage");
  const remaining = document.getElementById("remainingStorage");
  const STORAGE_KEY = "charcount_storage_demo";

  // --- 保存処理 ---
  function save(){
    localStorage.setItem(STORAGE_KEY, textarea.value);
  }

  // --- 復元処理 ---
  function load(){
    const saved = localStorage.getItem(STORAGE_KEY);
    if(saved !== null){
      textarea.value = saved;
    }
  }

  // --- カウント更新 ---
  function update(){
    const rest = MAX - [...textarea.value].length;
    remaining.textContent = rest;
    remaining.style.color = rest < 0 ? "red" : "black";
  }

  // イベント登録
  textarea.addEventListener("input", () => {
    save();
    update();
  });

  // ページロード時に復元
  load();
  update();
})();
</script>

● 実行例(デモ)

残り文字数: 100

● まとめ

  • localStorage を使えばページをまたいで入力内容を保持できる
  • ・入力ごとに自動保存し、リロードしても内容が復元される
  • ・長文入力や問い合わせフォームなど、ユーザー体験を大幅に改善できる
  • ・実務では保存期間やクリアのタイミングを設計に盛り込むことも大切

7. 実務Tips②:テスト観点チェックリスト

解説: 文字数カウント機能は「動けばOK」ではなく、さまざまな利用状況に対応できるかをテストで確認することが大切です。
特に入力フォームはユーザー接点が多いため、バグや仕様漏れがUXに直結します。
ここでは、実務でチェックしておきたい観点をチェックリスト化し、localStorageで保存・復元できるUIにしています。
進捗管理ツールとしても活用できます。




● テスト観点チェックリスト


● まとめ

  • 文字数カウントは境界値(0文字・上限・超過)の確認が必須
  • 絵文字・URL・複数フィールドなど特殊ケースを忘れずにテスト
  • チェックリストをlocalStorageで保存すれば、確認進捗も管理できる
  • アクセシビリティや保存復元など、実務ならではの観点を取り込むと品質が高まる

8. まとめ & 次への導線

解説: 文字数カウントはシンプルに見えて、実務では多くの工夫や注意点が必要でした。
基本の string.length から始まり、上限超過UIアクセシビリティ対応複数フィールド制御保存と復元 まで一通り実装方法を整理しました。
さらに最後にテスト観点チェックリストを導入することで、実務レベルでも安心して使える形に仕上げられます。




● 本記事のポイント

  • 基本: string.length[...text].length の違いを理解する
  • UI制御: 上限超過時の赤表示・エラーメッセージ・送信ボタン無効化
  • アクセシビリティ: aria-live / aria-invalid / role="alert" の活用
  • 実務Tips: URL換算や複数フィールドの合算、localStorage保存
  • テスト観点: チェックリストで抜け漏れを防ぐ

● 次への導線

本記事では「文字数カウント」という身近なテーマを通じて、フロントエンド実装の基礎と実務的な工夫を学びました。
次のステップとしておすすめなのは:

  • フォーム入力のバリデーション(必須チェック・正規表現・カスタムエラーメッセージ)
  • 非同期通信との連携(API送信前に入力チェックを挟む)
  • UI/UX改善(リアルタイムプレビューや入力補助など)

これらを組み合わせることで、より実践的な「ユーザーフレンドリーなフォーム開発」へと発展させられます。


● 最後に

文字数カウントは単なるおまけ機能ではなく、入力体験を大きく左右する重要な要素です。
本記事で紹介した仕組みをベースに、ぜひあなたのプロジェクトに組み込んでみてください!