目次⇒ 導入+基本編 応用:上限超過のUI制御 アクセシビリティ対応 実務Tips①:数え方の落とし穴 応用:複数フィールドやSNS風カウント 保存と復元(localStorage活用) 実務Tips②:テスト観点チェックリスト まとめ&次への導線 TOP
この記事では、dom操作やイベント処理など、JavaScript / TypeScriptと紹介した記事の内容を活用したコーディング例を紹介します。
今回は、文字数カウントを実装する例を紹介します。
解説:
Webフォームでは「入力できる文字数の制限」を設けることがよくあります。
例えば Twitter のように「140文字まで」といった制限がある場合、ユーザーが入力しながら残り文字数を確認できるUIは必須です。
このセクションでは、リアルタイム文字数カウントを最小限のコードで実装し、その仕組みを理解します。
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>下の入力欄に文字を入力して、残り文字数が即座に変化する様子を確認してください。
解説:
文字数カウントの基本形に、上限超過時のフィードバックを追加します。
残数がマイナスになったら赤く表示し、エラーメッセージを出したり、送信ボタンを無効化することで、
ユーザーが制限を超えて入力できないようにする工夫が重要です。
上限を 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字)を超えると残数が赤くなり、送信ボタンが無効化されます。
解説:
文字数カウントを実装する際には、見た目だけでなくアクセシビリティにも配慮する必要があります。
特に、スクリーンリーダー利用者や色覚に個性のあるユーザーにとっては、「残り文字数を音声で読み上げる」「赤色だけに頼らない通知」が重要です。
ここでは、aria-live 属性や aria-invalid を活用した、アクセシブルな文字数カウントを作ってみましょう。
以下のポイントを実装します:
・残り文字数を 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>
解説:
一見シンプルに見える「文字数カウント」ですが、実務では「何を1文字と数えるか」が意外と難しいポイントです。
JavaScriptの string.length は「UTF-16コード単位の数」を返すため、絵文字やサロゲートペアを含むとズレが発生します。
また、X(旧Twitter)のようにURLを固定長(23文字)で換算する仕様もあります。
ここでは、代表的な落とし穴と対策を紹介します。
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>
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>
解説:
実務では「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>
解説:
入力途中でブラウザを閉じても、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>
解説:
文字数カウント機能は「動けばOK」ではなく、さまざまな利用状況に対応できるかをテストで確認することが大切です。
特に入力フォームはユーザー接点が多いため、バグや仕様漏れがUXに直結します。
ここでは、実務でチェックしておきたい観点をチェックリスト化し、localStorageで保存・復元できるUIにしています。
進捗管理ツールとしても活用できます。
解説:
文字数カウントはシンプルに見えて、実務では多くの工夫や注意点が必要でした。
基本の string.length から始まり、上限超過UI、アクセシビリティ対応、複数フィールド制御、保存と復元 まで一通り実装方法を整理しました。
さらに最後にテスト観点チェックリストを導入することで、実務レベルでも安心して使える形に仕上げられます。
本記事では「文字数カウント」という身近なテーマを通じて、フロントエンド実装の基礎と実務的な工夫を学びました。
次のステップとしておすすめなのは:
これらを組み合わせることで、より実践的な「ユーザーフレンドリーなフォーム開発」へと発展させられます。
文字数カウントは単なるおまけ機能ではなく、入力体験を大きく左右する重要な要素です。
本記事で紹介した仕組みをベースに、ぜひあなたのプロジェクトに組み込んでみてください!