Building a Calculator with JavaScript
Creating a functional calculator is a classic project for anyone learning JavaScript. It’s an excellent way to solidify your understanding of DOM manipulation, event handling, and basic arithmetic logic. This guide will walk you through the process, providing a structured approach and clear code examples to help you build your very own web-based calculator.
By the end of this tutorial, you'll have a working calculator that can perform addition, subtraction, multiplication, and division, along with clear and reset functionalities.
The Core Components
A calculator project typically involves three main parts:
- HTML Structure: To define the layout and the buttons.
- CSS Styling: To make it visually appealing and functional on the screen.
- JavaScript Logic: To handle user interactions, perform calculations, and update the display.
1. HTML Markup: The Calculator's Skeleton
First, we need the fundamental structure for our calculator. This includes a display area for numbers and results, and a grid of buttons for numbers, operators, and control functions.
<div class="calculator">
<div class="calculator-display">0</div>
<div class="calculator-buttons">
<button class="clear-btn">AC</button>
<button class="operator-btn" data-operator="+/-">+/-</button>
<button class="operator-btn" data-operator="%">%</button>
<button class="operator-btn" data-operator="/">÷</button>
<button class="number-btn>7</button>
<button class="number-btn>8</button>
<button class="number-btn>9</button>
<button class="operator-btn" data-operator="*">×</button>
<button class="number-btn>4</button>
<button class="number-btn>5</button>
<button class="number-btn>6</button>
<button class="operator-btn" data-operator="-">-</button>
<button class="number-btn>1</button>
<button class="number-btn>2</button>
<button class="number-btn>3</button>
<button class="operator-btn" data-operator="+">+</button>
<button class="number-btn zero">0</button>
<button class="number-btn" data-decimal>.</button>
<button class="equals-btn">=</button>
</div>
</div>
Notice the use of data-operator and data-decimal attributes. These will be crucial for our JavaScript to easily identify the type of button being pressed.
2. CSS Styling: Making it Presentable (Optional but Recommended)
While this tutorial focuses on JavaScript, good styling is essential for user experience. Here's a basic CSS structure to give your calculator a functional appearance. You can expand on this significantly for a more polished look.
.calculator {
width: 320px;
margin: 50px auto;
border: 1px solid #ccc;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0,0,0,0.1);
font-family: Arial, sans-serif;
background-color: #333;
}
.calculator-display {
background-color: #222;
color: #fff;
font-size: 3em;
padding: 20px;
text-align: right;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
overflow: hidden; /* To handle long numbers */
}
.calculator-buttons {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1px; /* Small gap for visual separation */
}
.calculator-buttons button {
background-color: #e0e0e0;
border: none;
padding: 20px;
font-size: 1.5em;
cursor: pointer;
transition: background-color 0.2s;
border-radius: 0; /* Resetting default button radius */
}
.calculator-buttons button:hover {
background-color: #d0d0d0;
}
.operator-btn {
background-color: #f69906;
color: #fff;
}
.operator-btn:hover {
background-color: #e28100;
}
.equals-btn {
background-color: #f69906;
color: #fff;
grid-column: span 2; /* Make equals button span two columns */
}
.equals-btn:hover {
background-color: #e28100;
}
.clear-btn {
background-color: #a0a0a0;
color: #fff;
}
.clear-btn:hover {
background-color: #909090;
}
.zero {
grid-column: span 2;
}
3. JavaScript Logic: Bringing it to Life
This is where the magic happens. We'll manage the state of the calculator, handle button clicks, and perform calculations.
a. Setting Up Variables and DOM Elements
First, get references to all the necessary HTML elements and initialize variables to store the current state of the calculation.
const display = document.querySelector('.calculator-display');
const numberButtons = document.querySelectorAll('.number-btn');
const operatorButtons = document.querySelectorAll('.operator-btn');
const equalsButton = document.querySelector('.equals-btn');
const clearButton = document.querySelector('.clear-btn');
const decimalButton = document.querySelector('[data-decimal]');
let currentInput = '0';
let previousInput = '';
let operator = null;
let shouldResetDisplay = false; // Flag to clear display after an operation or equals
b. Updating the Display
A simple function to keep the calculator display updated.
function updateDisplay() {
display.textContent = currentInput;
}
c. Handling Number Inputs
When a number button is clicked, we append the number to currentInput. We also handle the shouldResetDisplay flag.
function appendNumber(number) {
if (shouldResetDisplay) {
currentInput = number;
shouldResetDisplay = false;
} else if (currentInput === '0' && number !== '.') {
currentInput = number; // Replace initial '0' unless it's a decimal
} else {
currentInput += number;
}
updateDisplay();
}
numberButtons.forEach(button => {
button.addEventListener('click', () => {
appendNumber(button.textContent);
});
});
d. Handling Decimal Input
Ensure only one decimal point can be entered in the current number.
decimalButton.addEventListener('click', () => {
if (shouldResetDisplay) {
currentInput = '0.';
shouldResetDisplay = false;
} else if (!currentInput.includes('.')) {
currentInput += '.';
}
updateDisplay();
});
e. Handling Operator Inputs
When an operator is clicked, we store the current input as previousInput, set the operator, and prepare to clear the display for the next number.
function chooseOperator(selectedOperator) {
if (operator !== null && !shouldResetDisplay) {
// If an operator was already selected and we're entering a new one without pressing '=',
// perform the previous calculation first.
calculate();
}
operator = selectedOperator;
previousInput = currentInput;
shouldResetDisplay = true; // Next number will clear the display
}
operatorButtons.forEach(button => {
button.addEventListener('click', () => {
chooseOperator(button.dataset.operator);
});
});
f. Performing the Calculation
This is the core logic. It takes previousInput, currentInput, and the operator, then performs the math.
function calculate() {
let result;
const prev = parseFloat(previousInput);
const current = parseFloat(currentInput);
if (isNaN(prev) || isNaN(current)) return; // Don't calculate if inputs are invalid
switch (operator) {
case '+':
result = prev + current;
break;
case '-':
result = prev - current;
break;
case '*':
result = prev * current;
break;
case '/':
if (current === 0) {
alert("Cannot divide by zero!");
result = 'Error';
} else {
result = prev / current;
}
break;
case '+/-': // Handle sign change
if (currentInput !== '0') {
result = current * -1;
} else {
result = 0;
}
break;
case '%': // Percentage calculation (e.g., 100 * 5%)
result = prev * (current / 100);
break;
default:
return;
}
currentInput = result.toString();
operator = null;
previousInput = '';
shouldResetDisplay = true;
updateDisplay();
}
equalsButton.addEventListener('click', calculate);
g. Clearing the Calculator
The "AC" (All Clear) button resets all variables to their initial state.
function clearCalculator() {
currentInput = '0';
previousInput = '';
operator = null;
shouldResetDisplay = false;
updateDisplay();
}
clearButton.addEventListener('click', clearCalculator);
h. Initial Setup
Call updateDisplay() once to ensure the display starts with '0'.
updateDisplay();
Putting It All Together (Full JavaScript Code)
Here’s the complete JavaScript file incorporating all the functionalities discussed above.
const display = document.querySelector('.calculator-display');
const numberButtons = document.querySelectorAll('.number-btn');
const operatorButtons = document.querySelectorAll('.operator-btn');
const equalsButton = document.querySelector('.equals-btn');
const clearButton = document.querySelector('.clear-btn');
const decimalButton = document.querySelector('[data-decimal]');
let currentInput = '0';
let previousInput = '';
let operator = null;
let shouldResetDisplay = false;
function updateDisplay() {
display.textContent = currentInput;
}
function appendNumber(number) {
if (shouldResetDisplay) {
currentInput = number;
shouldResetDisplay = false;
} else if (currentInput === '0' && number !== '.') {
currentInput = number;
} else {
currentInput += number;
}
updateDisplay();
}
function chooseOperator(selectedOperator) {
if (operator !== null && !shouldResetDisplay) {
calculate();
}
operator = selectedOperator;
previousInput = currentInput;
shouldResetDisplay = true;
}
function calculate() {
let result;
const prev = parseFloat(previousInput);
const current = parseFloat(currentInput);
if (isNaN(prev) || isNaN(current)) return;
switch (operator) {
case '+':
result = prev + current;
break;
case '-':
result = prev - current;
break;
case '*':
result = prev * current;
break;
case '/':
if (current === 0) {
alert("Cannot divide by zero!");
result = 'Error';
} else {
result = prev / current;
}
break;
case '+/-':
if (currentInput !== '0' && currentInput !== 'Error') {
currentInput = (parseFloat(currentInput) * -1).toString();
updateDisplay();
return; // Don't reset other calculation state
}
return;
case '%':
if (previousInput === '') { // If only current number, calculate percentage of itself
result = current / 100;
} else {
result = prev * (current / 100);
}
break;
default:
return;
}
currentInput = result.toString();
operator = null;
previousInput = '';
shouldResetDisplay = true;
updateDisplay();
}
function clearCalculator() {
currentInput = '0';
previousInput = '';
operator = null;
shouldResetDisplay = false;
updateDisplay();
}
// Event Listeners
numberButtons.forEach(button => {
button.addEventListener('click', () => {
appendNumber(button.textContent);
});
});
operatorButtons.forEach(button => {
button.addEventListener('click', () => {
chooseOperator(button.dataset.operator);
});
});
equalsButton.addEventListener('click', () => {
calculate();
});
clearButton.addEventListener('click', () => {
clearCalculator();
});
decimalButton.addEventListener('click', () => {
if (shouldResetDisplay) {
currentInput = '0.';
shouldResetDisplay = false;
} else if (!currentInput.includes('.')) {
currentInput += '.';
}
updateDisplay();
});
// Initialize display
updateDisplay();
Refinements and Next Steps
While this calculator is fully functional, there are always ways to improve and add more features:
- Keyboard Support: Add event listeners for keyboard input to make the calculator more user-friendly.
- Error Handling: Improve error display (e.g., "Error" for division by zero) and prevent further calculations until cleared.
- Complex Operations: Implement features like square root, power, memory functions (M+, M-, MR, MC).
- History: Keep a log of previous calculations.
- Responsiveness: Ensure the calculator looks good on various screen sizes.
- The '+/-' and '%' Operators: My implementation for '+/-' and '%' within `calculate` is a bit simplistic. A more robust approach might be to handle them as immediate operations that modify `currentInput` without waiting for another number or equals, similar to how they function on physical calculators.
Conclusion
Building a calculator is a fantastic exercise that touches upon many fundamental JavaScript concepts. You've learned how to structure your HTML, apply basic styling, and most importantly, how to use JavaScript to manipulate the DOM, handle user events, manage application state, and perform logical operations. This project serves as a solid foundation for more complex web applications, empowering you to create interactive and dynamic user interfaces.