ФронтендJavaScriptHTML / CSS

Как сделать калькулятор на JavaScript: считаем прямо на странице

Делаем калькулятор на JavaScript без eval: дисплей, цифровые кнопки, четыре операции, дроби, сброс, обработка ошибок и проверка результата в браузере.

Кодик

Автор

5 мин чтения

Веб-калькулятору не нужны фреймворки и eval. Один HTML-файл будет хранить текущее число, первый операнд и знак, а затем точно выполнит выбранное действие.

В кнопочном калькуляторе пользователь не вводит готовую строку 12 + 3. Каждый клик меняет состояние: сначала растёт текущее число, затем сохраняются операнд и знак. После знака равенства полученное число становится новым текущим.

1Храним

current, previous и operator описывают всё состояние.

2Меняем

Кнопки вызывают три короткие функции по типу действия.

3Рисуем

render переносит current на дисплей после каждого клика.

Какое состояние нужно калькулятору

Вместо парсинга длинной строки мы будем знать, на каком шаге находится человек. Цифра дописывается к current, знак переносит current в previous, а равенство вызывает одну функцию compute. Точка добавляется только один раз.

ПеременнаяПримерСмысл
current12.5То, что видно на экране
previous8Первый операнд
operator+Ожидающее действие
justComputedtrueСледующая цифра начнёт новое число

После этого шага у каждой части есть одна роль. Интерфейс собирает действие, логика меняет состояние, а вывод показывает признак успеха. Это проще отлаживать, чем один большой обработчик.

Что происходит после клика
Состояние меняется по шагам. Один обработчик обслуживает всю сетку

Сначала соберите минимальный сценарий. Не добавляйте оформление, хранение и дополнительные режимы одновременно. Добейтесь одного правильного прохода от входных данных до результата, а затем проверяйте каждое улучшение отдельно.

Собираем HTML, CSS и JavaScript

Кнопкам не нужны отдельные id. Атрибут data-action говорит, какой это тип кнопки, а data-value хранит цифру или знак. Один обработчик на контейнере ловит все клики и выбирает нужную ветку.

<div class="calculator" id="calculator">
  <output class="display" id="display">0</output>
  <div class="keys">
    <button data-action="clear">C</button>
    <button data-action="operator" data-value="/">÷</button>
    <button data-action="operator" data-value="*">×</button>
    <button data-action="operator" data-value="-">−</button>
    <button data-value="7">7</button><button data-value="8">8</button>
    <button data-value="9">9</button><button data-action="operator" data-value="+">+</button>
    <button data-value="4">4</button><button data-value="5">5</button>
    <button data-value="6">6</button><button data-action="equals">=</button>
    <button data-value="1">1</button><button data-value="2">2</button>
    <button data-value="3">3</button><button data-value="0">0</button>
    <button data-value=".">.</button>
  </div>
</div>
<script>
const display = document.querySelector('#display');
let current = '0', previous = null, operator = null, justComputed = false;

function render() { display.value = current; display.textContent = current; }
function append(value) {
  if (justComputed) { current = '0'; justComputed = false; }
  if (value === '.' && current.includes('.')) return;
  current = current === '0' && value !== '.' ? value : current + value;
}
function compute() {
  if (previous === null || operator === null) return;
  const a = Number(previous), b = Number(current);
  const actions = { '+': (x,y)=>x+y, '-': (x,y)=>x-y,
    '*': (x,y)=>x*y, '/': (x,y)=>y === 0 ? NaN : x/y };
  const result = actions[operator](a, b);
  current = Number.isFinite(result) ? String(Number(result.toFixed(10))) : 'Ошибка';
  previous = operator = null; justComputed = true;
}
document.querySelector('#calculator').addEventListener('click', event => {
  const button = event.target.closest('button'); if (!button) return;
  const { action, value } = button.dataset;
  if (action === 'clear') { current='0'; previous=operator=null; }
  else if (action === 'operator') { previous=current; operator=value; justComputed=true; }
  else if (action === 'equals') compute();
  else append(value);
  render();
});
</script>
Что должно работать после первого запуска

Цифры и точка собирают текущее число. Операция запоминает первый операнд. Деление на ноль показывает ошибку без Infinity. Кнопка C возвращает начальное состояние.

Два подхода к вычислению
Выбираем контролируемую модель. Сравнение показывает, какой вариант легче поддерживать и проверять.

Как работает цепочка вычислений

После расчёта на дисплее остаётся ответ. Если нажать знак, ответ можно использовать дальше. Если нажать цифру, начнётся новое число. Это решает флаг justComputed, который не даёт склеить ответ со следующей цифрой.

КликСостояние доСостояние после
7current = 0current = 7
+current = 7previous = 7, operator = +
3justComputed = truecurrent = 3
=7, +, 3current = 10
2justComputed = truecurrent = 2

Сохраняйте в одном месте то, что может измениться. Если одно и то же значение скопировано в три функции, после первой правки они начнут расходиться. Один объект состояния и одна функция отрисовки снимают эту проблему.

Смотрите на состояние, а не только на красивый экран. Перезагрузка, повторный клик, пустой ввод и медленный ответ часто открывают ошибки, которые не видны в идеальном демонстрационном проходе.

Практика: клики, дроби и ноль

Проверяйте не только арифметику, но и последовательность кликов. Дважды нажатая точка не должна ломать Number, а новая цифра после равенства не должна дописываться к старому ответу.

Проверка своими руками
  1. Откройте index.html и наберите 12 + 3 =.
  2. Нажмите × 4 =, чтобы продолжить цепочку от ответа 15.
  3. Нажмите C, введите 2.5 × 4.
  4. Нажмите точку дважды и убедитесь, что на экране одна точка.
  5. Поделите 8 на 0, затем нажмите C.
  6. Откройте DevTools и проверьте, что в консоли нет ошибок.
Готово, если выполняются все пункты

Все кнопки работают мышью и касанием. Цепочка 12 + 3 × 4 даёт 60 в пошаговой модели. Дробь и деление на ноль обрабатываются без сбоя. Кнопка C сбрасывает все три части состояния.

Проверка калькулятора
Четыре сценария до готового дизайна. Пройдите все четыре сценария перед публикацией проекта.

Доводим интерфейс до готового вида

Когда логика готова, сделайте сетку кнопок одинаковой и оставьте операциям один акцентный цвет. Дисплей должен помещать длинное число, поэтому не задавайте ему жёсткую высоту без overflow. Для телефона кнопкам нужна высота около 48 пикселей, чтобы по ним было легко попасть.

eval для всей строки

Выполнять текст не нужно. Храните операнды и знак отдельно.

Две точки

Перед добавлением проверьте current.includes.

Infinity на экране

Обработайте ноль до деления или проверьте Number.isFinite.

Десятичный хвост

После операции округлите незначащий шум float.

Сверьтесь с первичным источником. Поведение используемых функций и ограничения примера проверяйте по официальной документацией. Документация особенно важна, когда меняются версии, политика платформы или формат ответа API.

Что получится в итоге

Готовый результат проекта: Как сделать калькулятор на JavaScript: считаем прямо на странице
Готовый калькулятор умещается в одной карточке и сразу готов к проверке в браузере.

Короткие ответы
Почему не нужен eval?

Все допустимые операции известны. Объект с четырьмя функциями решает задачу без выполнения строки.

Зачем justComputed?

Флаг отличает результат от числа, которое пользователь сейчас набирает.

Как запретить вторую точку?

Перед добавлением проверить current.includes('.').

Как понять, что проект готов?

Он повторяет целевой сценарий несколько раз подряд, понятно реагирует на ошибочный ввод и не теряет состояние при обычных действиях.

Можно ли копировать код целиком?

Можно как стартовую точку, но сначала замените значения из примера, затем запустите проект по шагам. Если вы не можете объяснить одну строку, её лучше разобрать до следующего шага.

Научите интерфейс жить по событиям

События, DOM и состояние разбираем по шагам в курсе JavaScript. Если скрипт не запускается, сверьте подключение со статьё про script.

Для следующего проекта возьмите Fetch API или localStorage. В Кодике код можно запускать прямо на телефоне.