In this tutorial I will share how I built simple Javascript Calculator from scratch. I will explain all the logic and steps in details.
My goal was to practice the skill of building simple Javascript calculator, but at the same time to make it fully functional and to cover all possible edge cases.
Frankly speaking, it was harder than I thought 🤔, as tutorials I found, were incomplete. Therefore some solutions and edge cases I had to find myself.
As a UI designer, I decided to style JS calculator in Windows 95/98 UI style 🤟.
So, let’s go and see the project in action below ⬇️
Project: Simple Javascript Calculator
// how to make javascript calculator
See the Pen Untitled by jsSecrets (@jssecrets) on CodePen.
Table of Contents
Basic Working Principle
// how to build javascript calculator
What is JavaScript Calculator in a nutshell?
Basic calculator can add, subtract, multiply and divided numbers.
More advanced calculators have more functions like: power, square root, percents, etc.
Working Principle
Simplest Working Version (SWV)
Math functions
The simplest working version of JS calculator should have four basic math functions:
1️⃣ Addition
2️⃣ Subtraction
3️⃣ Multiplication
4️⃣ Division
Equal function
Edge cases
For instance, division by zero.
Core concept
// how to build a javascript calculator app
Let’s continue with a core concept of how the JS calculator works.
Understanding the core concept is crucial part. Because when you know the principles – the methods can be different.
1️⃣ HTML
We have screen and buttons.
2️⃣ CSS
I used CSS Grid to layout all the buttons. Everything else is basic CSS.
3️⃣ Javascript
All the work is done here 😊
In our calculator there are four parts to consider.
1️⃣ to recognize a first entered number
2️⃣ to recognize the operation (+, -, * or /)
3️⃣ to recognize a second entered number
4️⃣ connect 1, 2 and 3 with “equals”
? cover edge cases and non-straight forward user’s behavior
Code explanation
// how to build javascript calculator
HTML
// how to code a javascript calculator
<body>
<div class="calculator">
<div class="screen mb16">
<div class="result">0</div>
</div>
<div class="buttons">
<button data-digit="7" class="btn btn-primary blue">7</button>
<button data-digit="8" class="btn btn-primary blue">8</button>
<button data-digit="9" class="btn btn-primary blue">9</button>
<button data-operation="+" class="btn btn-primary red">+</button>
<button data-digit="4" class="btn btn-primary blue">4</button>
<button data-digit="5" class="btn btn-primary blue">5</button>
<button data-digit="6" class="btn btn-primary blue">6</button>
<button data-operation="-" class="btn btn-primary red">-</button>
<button data-digit="1" class="btn btn-primary blue">1</button>
<button data-digit="2" class="btn btn-primary blue">2</button>
<button data-digit="3" class="btn btn-primary blue">3</button>
<button data-operation="*" class="btn btn-primary red">*</button>
<button data-digit="0" class="btn btn-primary blue">0</button>
<button data-clear class="btn btn-primary red-dark">C</button>
<button data-equals class="btn btn-primary red">=</button>
<button data-operation="/" class="btn btn-primary red">/</button>
</div>
</div>
</body>
Here I have created main container <div class="calculator">
and it contains two children containers: <div class="screen">
and <div class="buttons">
.
<div class="screen">
contains a result of the operations.
<div class="buttons">
are buttons.
Deeper dive into buttons markup
// how to create a javascript calculator
<!-- digit buttons -->
<button data-digit="7" class="btn btn-primary blue">7</button>
<button data-digit="2" class="btn btn-primary blue">7</button>
<!-- operation buttons -->
<button data-operation="+" class="btn btn-primary red">+</button>
<button data-operation="*" class="btn btn-primary red">+</button>
<!-- clear button -->
<button data-clear class="btn btn-primary red-dark">C</button>
<!-- equals button -->
<button data-equals class="btn btn-primary red">=</button>
Every button has a <button data-ATTRIBUTE="VALUE">
attribute which will be used in Javascript for selecting purposes.
And every attribute is descriptive
digit is <button data-digit="VALUE">
operation is <button data-operation="VALUE">
.
✅ It is a good practice to have descriptive names for variables, classes, etc.
CSS
// how to design a javascript calculator
/* Basic reset */
*,
::before,
::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
ul {
list-style: none;
}
/* font loading */
@font-face {
font-family: "Windows 95";
src: url('https://1674814792-20d01e3358a6b87a.wp-transfer.sgvps.net/wp-content/uploads/2022/08/w95.woff2')
format('woff2');
font-weight: normal;
font-style: normal;
}
.red {
color: #ff0000;
}
.blue {
color: #0000ff;
}
.red-dark {
color: #800000;
}
* {
font-family: "Windows 95", sans-serif;
}
body {
height: 100vh;
background: #eaeaea;
position: relative;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
}
.calculator {
padding: 24px;
background: #c0c0c0;
border-width: 3px;
border-style: solid;
border-right-color: #606f6f;
border-bottom-color: #606f6f;
border-top-color: #f1f1f1;
border-left-color: #f1f1f1;
}
.calculator .screen .result {
width: 100%;
max-width: 100%;
height: 68px;
text-align: right;
padding: 16px;
font-size: 32px;
font-weight: bold;
color: #000;
background-color: #fff;
border-width: 3px;
border-style: solid;
border-right-color: #f1f1f1;
border-bottom-color: #f1f1f1;
border-top-color: #606f6f;
border-left-color: #606f6f;
}
.calculator .buttons {
/*CSS grid to layout all buttons */
display: grid;
gap: 8px;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: repeat(4, 1fr);
}
.calculator .buttons button {
font-size: 32px;
font-weight: bold;
}
.calculator .buttons button:hover {
cursor: pointer;
}
/* button styles are copied from external css library */
.btn {
position: relative;
transition: all 0.2s ease;
letter-spacing: 0.025em;
padding: 0.7rem 2.1rem;
}
.btn:before,
.btn:after {
content: "";
position: absolute;
box-sizing: border-box;
display: block;
background: transparent;
z-index: 9;
top: 0;
left: 0;
}
.btn:before {
width: 100%;
height: 100%;
border-top: 2px solid #f1f1f1;
border-left: 2px solid #f1f1f1;
border-right: 2px solid #606f6f;
border-bottom: 2px solid #606f6f;
}
.btn:not(:disabled):not(.disabled):active:before,
.btn:not(:disabled):not(.disabled):active:focus:before {
border-color: transparent;
}
.btn-primary {
background-color: #c0c0c0;
border-color: #a0a0a0;
}
.btn-primary:hover {
background-color: #c0c0c0;
border-color: #a0a0a0;
}
.btn-primary:focus,
.btn-primary.focus {
box-shadow: none;
}
There is no too many special styling in my JS calculator project.
First is basic CSS reset.
Then I used CSS Grid to layout all the buttons.
For the Windows 95/98 UI style I used styles from external library (link is at the end of the article)
JS
// how to form a javascript calculator
// Variables
let a = '';
let b = '';
let currentOperation = '';
let isCompleted = false;
const result = document.querySelector('.result');
const digits = document.querySelectorAll('[data-digit]');
const operations = document.querySelectorAll('[data-operation]');
const equalsBtn = document.querySelector('[data-equals]');
const clearBtn = document.querySelector('[data-clear]');
// Functions
// function for edge cases
const edgeCase = (message) => {
result.textContent = message;
a = '';
b = '';
currentOperation = '';
isCompleted = false;
};
const clearAll = () => {
edgeCase('0');
};
const cleanScreen = () => {
result.textContent = '';
};
const equalsFunction = () => {
if (b === '') b = a;
switch (currentOperation) {
case '+':
a = parseInt(a, 10) + parseInt(b, 10);
break;
case '-':
a = parseInt(a, 10) - parseInt(b, 10);
break;
case '*':
a = parseInt(a, 10) * parseInt(b, 10);
break;
case '/':
if (b === '0') {
edgeCase('Error');
return;
}
a = parseInt(a, 10) / parseInt(b, 10);
break;
}
isCompleted = true;
// overflow check
if (a >= 1e20 || a <= -1e20) {
edgeCase('Overflow');
a = 0;
return;
}
result.textContent = a;
};
const enterOperation = (event) => {
cleanScreen();
// if currentOperation entered before first number
if (a === '' && b === '') {
a = 0;
}
// if equals btn is omited and operation btn used
if (a !== '' && b !== '' && currentOperation !== '' && !isCompleted) {
equalsFunction();
currentOperation = event.currentTarget.dataset.operation;
return;
}
currentOperation = event.currentTarget.dataset.operation;
result.textContent = currentOperation;
};
const enterDigit = (event) => {
//
let symbol = event.currentTarget.dataset.digit;
cleanScreen();
if (b === '' && currentOperation === '') {
a += symbol;
result.textContent = parseInt(a, 10);
if (a >= 1e20 || a <= -1e20) {
result.textContent = 'Overflow';
a = '';
return;
}
}
/////////////////
else if (a !== '' && b !== '' && isCompleted) {
b = symbol;
isCompleted = false;
result.textContent = parseInt(b, 10);
}
/////////////////
else {
b += symbol;
result.textContent = parseInt(b, 10);
if (b >= 1e20 || b <= -1e20) {
result.textContent = 'Overflow';
b = '';
return;
}
}
};
// Event Listeners
clearBtn.addEventListener('click', clearAll);
digits.forEach((number) => {
number.addEventListener('click', enterDigit);
});
operations.forEach((operation) => {
operation.addEventListener('click', enterOperation);
});
equalsBtn.addEventListener('click', equalsFunction);
The JS code is the main focus of the project, so I will break down step by step on how I have built the calculator.
Variables
// how to make a javascript calculator app
//// Variables
// variable "a" will hold a first number
let a = '';
// variable "b" will hold a second number
let b = '';
// variable "currentOperation" will hold a current operation (+,-,* or /)
let currentOperation = '';
// variable "isCompleted" is needed for operation logic purposes
// operation is completed or not
let isCompleted = false;
// DOM elements
const result = document.querySelector('.result');
const digits = document.querySelectorAll('[data-digit]');
const operations = document.querySelectorAll('[data-operation]');
const equalsBtn = document.querySelector('[data-equals]');
const clearBtn = document.querySelector('[data-clear]');
Functions
Clean screen function
// cleanScreen() function
// it cleans the result screen
// it is used as a utility "invisible" function
// to create effect of concating digits into number
const cleanScreen = () => {
result.textContent = '';
};
Edge case function
// edgeCase() function
// this is sort of "zeroing"
// reverts everything to default state
// there are some edge cases
// - division by 0
// - overflow
// the "message" parameter is used
// as we have a different messages to display
// in each case
// and as you can see
// all variables go back to original state
const edgeCase = (message) => {
result.textContent = message;
a = '';
b = '';
currentOperation = '';
isCompleted = false;
};
// reusable functions are good practice
// create once - use many times
// DRY - Don't Repeat Yourself ?
Clear all function
// clearAll() function
// this function is attached to "C" button
// it sets everything to its original state
const clearAll = () => {
edgeCase('0');
};
Digit enter function
// enterDigit() function
// this functions attached only to the digit buttons
const enterDigit = (event) => {
// we use "event" argument to be able
// to retrieve the data from the digit
// which is clicked
// (as we have more than one "digit"element)
// "event.currentTarget" allows us to do that
// "symbol" is local variable that holds
// digit character from the button clicked
// the value is taken from
// data-digit="VALUE" attribute
let symbol = event.currentTarget.dataset.digit;
// utility function (explained above)
cleanScreen();
// first number input
// check is: if second number is empty
// and operation is empty
// then add a entered symbol into first number
if (b === '' && currentOperation === '') {
a += symbol;
// "result" pane will display first number
// parseInt(a, 10) is used to prevent
// leading zero case (02078)
// as JS removes it by default
// parseInt(02078, 10) = 2078
result.textContent = parseInt(a, 10);
// simple overflow protection
// during input phase
// if number is too big or small
// the message appears
if (a >= 1e20 || a <= -1e20) {
result.textContent = 'Overflow';
a = '';
return;
}
}
// this case comes into play
// when the very first operation is completed
// check is:
// if first number is not empty
// and if second number is not empty
// and our isCompleted flag is true
// as you can see it will play only once
// the entered digit will go into second number
// and isCompleted flag will become "false"
// next digits will go into second number in a normal way
// see below
else if (a !== '' && b !== '' && isCompleted) {
b = symbol;
isCompleted = false;
result.textContent = parseInt(b, 10);
}
// second number input
// as you can see it is "else"
// all checks has been made
// code is the same as first number's
else {
b += symbol;
result.textContent = parseInt(b, 10);
// simple overflow protection
// during input phase
// if number is too big or small
// the message appears
if (b >= 1e20 || b <= -1e20) {
result.textContent = 'Overflow';
b = '';
return;
}
}
};
Equals function
// equalsFunction() function
const equalsFunction = () => {
// non-standard behavior
// when user enters a first number and operation
// and then clicks "=" button
// then second number is set to the first one
if (b === '') b = a;
// the core
// we determine what "currentOperation" variable holds
// and act accordingly
// parseInt(a, 10) is used to make sure
// variables act as Number
switch (currentOperation) {
case '+':
a = parseInt(a, 10) + parseInt(b, 10);
break;
case '-':
a = parseInt(a, 10) - parseInt(b, 10);
break;
case '*':
a = parseInt(a, 10) * parseInt(b, 10);
break;
case '/':
// simple "division by 0" check
if (b === '0') {
// edgeCase function (see above)
edgeCase('Error');
return;
}
a = parseInt(a, 10) / parseInt(b, 10);
break;
}
// we set "isCompleted" to "true"
// to know that the very first operation
// had taken place
isCompleted = true;
// simple overflow protection
// if the result is too big or small
// the message appears
if (a >= 1e20 || a <= -1e20) {
// edgeCase function (see above)
edgeCase('Overflow');
a = 0;
return;
}
result.textContent = a;
};
Operation enter function
// enterOperation() function
const enterOperation = (event) => {
// utility function (explained above)
cleanScreen();
// if operation is entered before first number
// then first number is set to 0
if (a === '' && b === '') {
a = 0;
}
// if equals "=" button is omited
// after first number, second number
// and operation are entered
// and operation button is pressed again
// ex: 3 + 8 and next is "*"
// then the latter operation will act as "equals" button
// as you can see equalsFunction() is invoked
if (a !== '' && b !== '' && currentOperation !== '' && !isCompleted) {
equalsFunction();
currentOperation = event.currentTarget.dataset.operation;
return;
}
currentOperation = event.currentTarget.dataset.operation;
result.textContent = currentOperation;
};
Event listeners
Here everything is pretty straight forward.
Event listeners to all digits and operations buttons are being attached through .forEach()
loop.
// Event Listeners
clearBtn.addEventListener('click', clearAll);
digits.forEach((number) => {
number.addEventListener('click', enterDigit);
});
operations.forEach((operation) => {
operation.addEventListener('click', enterOperation);
});
equalsBtn.addEventListener('click', equalsFunction);
Project Ideas
1️⃣ To expand this Javascript calculator further. To add decimal point, backspace and advanced operations like power function, etc.
2️⃣ To make calculator like in iPhones. When it is in portrait mode, the calculator is minimalistic. When the calc in landscape mode it displays all the functions.
3️⃣ Many ideas from Windows calculator
What to do next?
You can check some of my UI projects:
1️⃣ Javascript Custom Dropdown
3️⃣ Javascript Popup
Conclusion
It was my first Javascript calculator that I have built from scratch. I tried to cover all basic functions and edge cases. And I can say, that I am satisfied with my first experiment. Hopefully, in the future I will build the complete version with more functions.
Try to model this JS calculator and tell me in the comments how it went.
If you will find some bugs or some new edge cases also please let me know.
Resources
1️⃣ jssecrets.com
2️⃣ Work illustrations by Storyset
3️⃣ People illustrations by Storyset