ショッピングカートの合計金額、消費税の自動計算、BMI チェッカーなど、ユーザーが数値を入力するとリアルタイムで計算結果が更新される UI は Web アプリの定番です。JavaScript の input イベントと数値変換を組み合わせることで実装できます。
この記事でわかること
・input イベントでリアルタイムに値を取得する方法
・input イベントと change イベントの違い
・parseFloat / parseInt / Number による数値変換と NaN 対策
・消費税計算・BMI 計算・ショッピングカートの実装
・range スライダーとの連動
・toLocaleString で金額をフォーマットする方法
・バリデーションとエラー表示
・デバウンスによるパフォーマンス最適化
・input イベントでリアルタイムに値を取得する方法
・input イベントと change イベントの違い
・parseFloat / parseInt / Number による数値変換と NaN 対策
・消費税計算・BMI 計算・ショッピングカートの実装
・range スライダーとの連動
・toLocaleString で金額をフォーマットする方法
・バリデーションとエラー表示
・デバウンスによるパフォーマンス最適化
基本実装: 2 つの数値を足してリアルタイム表示する
HTML
<input type="number" id="num1" placeholder="数値1"> <input type="number" id="num2" placeholder="数値2"> <p>合計: <span id="result">0</span></p>
JavaScript
const num1 = document.getElementById("num1");
const num2 = document.getElementById("num2");
const result = document.getElementById("result");
function calculate() {
const a = parseFloat(num1.value) || 0;
const b = parseFloat(num2.value) || 0;
result.textContent = a + b;
}
// input イベント: キー入力のたびにリアルタイム発火
num1.addEventListener("input", calculate);
num2.addEventListener("input", calculate);
parseFloat(value) || 0 とすることで、未入力・無効な値(NaN)の場合は 0 として扱います。整数のみの場合は parseInt() を使います。input イベントと change イベントの違い
| イベント | 発火タイミング | 推奨場面 |
|---|---|---|
input |
キー入力・ペースト・スピンボタン操作のたびに発火 | リアルタイム計算・文字数カウンター |
change |
フォーカスが外れたとき(Enter / Tab)に発火 | 確定後の処理・重い計算 |
JavaScript
// リアルタイム(推奨)
input.addEventListener("input", calculate);
// 確定時のみ
input.addEventListener("change", calculate);
type="range"(スライダー)では input がスライド中に連続発火し、change はスライド終了後に 1 回だけ発火します。input.value の数値変換と NaN 対策
input.value は常に文字列です。計算に使う前に数値に変換する必要があります。
| 方法 | 空文字 | “3.14” | “123abc” | “abc” |
|---|---|---|---|---|
Number() |
0 | 3.14 | NaN | NaN |
parseFloat() |
NaN | 3.14 | 123 | NaN |
parseInt() |
NaN | 3 | 123 | NaN |
+value(単項+) |
0 | 3.14 | NaN | NaN |
NaN 対策パターン
// パターン1: || 0 でデフォルト値
const val1 = parseFloat(input.value) || 0;
// パターン2: Nullish coalescing(NaN には効かないので注意)
// parseFloat("") は NaN → || 0 が必要
// パターン3: isNaN でチェック
const raw = parseFloat(input.value);
const val2 = isNaN(raw) ? 0 : raw;
// パターン4: Number + || 0(空文字は0になるので安全)
const val3 = Number(input.value) || 0;
parseFloat("") || 0 と Number("") || 0 はどちらも 0 を返しますが、Number("0") || 0 も 0 です。ユーザーが意図的に 0 を入力した場合と空欄を区別する必要がある場合は isNaN で判定してください。実践例: 消費税計算機
HTML
<label>税抜価格: <input type="number" id="price" min="0"> 円</label> <p>消費税(10%): <span id="tax">0</span>円</p> <p>税込価格: <strong><span id="total">0</span>円</strong></p>
JavaScript
document.getElementById("price").addEventListener("input", function () {
const price = parseFloat(this.value) || 0;
const tax = Math.round(price * 0.1);
const total = price + tax;
document.getElementById("tax").textContent = tax.toLocaleString();
document.getElementById("total").textContent = total.toLocaleString();
});
toLocaleString() で数値に桁区切りカンマを追加できます(例: 1000 → “1,000”)。金額表示に使うと見やすくなります。実践例: BMI 計算機
HTML
<input type="number" id="height" placeholder="身長(cm)" min="1"> <input type="number" id="weight" placeholder="体重(kg)" min="1"> <p id="bmi-result">BMI: --</p> <p id="bmi-judge"></p>
JavaScript
function calcBMI() {
const h = parseFloat(document.getElementById("height").value);
const w = parseFloat(document.getElementById("weight").value);
if (!h || !w || h <= 0 || w <= 0) {
document.getElementById("bmi-result").textContent = "BMI: --";
document.getElementById("bmi-judge").textContent = "";
return;
}
const bmi = w / ((h / 100) ** 2);
const judge = bmi < 18.5 ? "低体重"
: bmi < 25 ? "標準"
: bmi < 30 ? "肥満1度" : "肥満2度以上";
document.getElementById("bmi-result").textContent = `BMI: ${bmi.toFixed(1)}`;
document.getElementById("bmi-judge").textContent = `判定: ${judge}`;
}
document.getElementById("height").addEventListener("input", calcBMI);
document.getElementById("weight").addEventListener("input", calcBMI);
実践例: ショッピングカートの合計計算
HTML
<table id="cart">
<thead><tr><th>商品</th><th>単価</th><th>数量</th><th>小計</th></tr></thead>
<tbody>
<tr>
<td>商品A</td>
<td data-price="500">500円</td>
<td><input type="number" class="qty" value="1" min="0" max="99"></td>
<td class="subtotal">500</td>
</tr>
<tr>
<td>商品B</td>
<td data-price="1200">1,200円</td>
<td><input type="number" class="qty" value="1" min="0" max="99"></td>
<td class="subtotal">1,200</td>
</tr>
</tbody>
<tfoot><tr><td colspan="3">合計</td><td id="total">1,700</td></tr></tfoot>
</table>
JavaScript
const cart = document.getElementById("cart");
cart.addEventListener("input", () => {
let total = 0;
cart.querySelectorAll("tbody tr").forEach(row => {
const price = Number(row.querySelector("[data-price]").dataset.price);
const qty = parseInt(row.querySelector(".qty").value) || 0;
const subtotal = price * qty;
row.querySelector(".subtotal").textContent = subtotal.toLocaleString();
total += subtotal;
});
document.getElementById("total").textContent = total.toLocaleString();
});
イベント委任を使って
cart に 1 つだけ input リスナーを設定しています。行を動的に追加しても、新しい入力フィールドに自動でイベントが適用されます。range スライダーと連動する
HTML
<label> 割引率: <input type="range" id="discount" min="0" max="50" value="10"> <span id="discountValue">10</span>% </label> <p>定価 10,000円 → 割引後: <span id="discountedPrice">9,000</span>円</p>
JavaScript
const slider = document.getElementById("discount");
const display = document.getElementById("discountValue");
const priceEl = document.getElementById("discountedPrice");
const basePrice = 10000;
slider.addEventListener("input", () => {
const rate = parseInt(slider.value);
display.textContent = rate;
const discounted = Math.round(basePrice * (1 - rate / 100));
priceEl.textContent = discounted.toLocaleString();
});
バリデーションとエラー表示
JavaScript
function getValidNumber(input, min = 0, max = Infinity) {
const value = parseFloat(input.value);
if (isNaN(value)) {
input.classList.add("is-invalid");
return null;
}
if (value < min || value > max) {
input.classList.add("is-invalid");
return null;
}
input.classList.remove("is-invalid");
return value;
}
CSS
.is-invalid {
border-color: #ef4444;
background-color: #fef2f2;
}
HTML の
type="number" にはブラウザのネイティブバリデーション(min / max / step)がありますが、ユーザーが直接テキストを入力した場合やブラウザによって挙動が異なるため、JavaScript でのバリデーションも併用してください。関連記事
- 計算と Math オブジェクトの使い方 — 四捨五入・乱数・浮動小数点
- 四捨五入の方法 — Math.round・toFixed
- フォーカスイベントの使い方 — blur バリデーション
- クリックイベントの設定方法
- getAttribute の使い方 — data 属性の取得
よくある質問
QparseInt と parseFloat の違いは何ですか?
A
parseInt は整数部分のみ(parseInt("3.14") → 3)、parseFloat は小数を含めて変換します(parseFloat("3.14") → 3.14)。金額や体重など小数が必要な場合は parseFloat、個数や枚数は parseInt を使います。Qinput イベントと change イベントはどう使い分けますか?
A
input はキー入力のたびに発火しリアルタイム計算に最適です。change はフォーカスが外れたとき(Enter / Tab)に発火し、確定後の処理や重い計算に使います。Q計算結果にカンマ区切りを付けるには?
A
number.toLocaleString() で桁区切りカンマが付きます(例: (1234567).toLocaleString() → "1,234,567")。小数桁数を固定したい場合は toLocaleString("ja-JP", { minimumFractionDigits: 2 }) を使います。Qユーザーが文字を入力した場合に計算結果が NaN になります。
A
parseFloat(value) || 0 のようにデフォルト値を設定するか、isNaN() でチェックして表示を「–」にするなどの対策をしてください。type="number" を設定しても、ブラウザによっては文字入力を完全には防げません。Q入力のたびに重い計算が実行されてパフォーマンスが悪いです。
A
input イベントにデバウンス(入力が止まってから一定時間後に実行)を適用してください。let timer; input.addEventListener("input", () => { clearTimeout(timer); timer = setTimeout(calculate, 300); });まとめ
input の入力値をリアルタイムで計算・表示する方法を整理しました。
- リアルタイム計算:
addEventListener("input", fn) - 数値変換:
parseFloat(value) || 0で NaN 対策 - 金額フォーマット:
toLocaleString()で桁区切りカンマ - バリデーション: isNaN チェック + min/max 範囲チェック
- イベント委任: 複数の入力フィールドを親要素で一括管理
消費税計算機、BMI チェッカー、ショッピングカート、スライダーなど、input のリアルタイム計算は Web アプリの基本パターンです。NaN 対策とバリデーションを忘れずに実装しましょう。