How to Build Javascript Calculator 🧮 [COMPLETE]

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.

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.

Javascript calculator
Javascript calculator

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.

js calculator
js 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 ?
Tip
It is a good practice to write a function if code used more than once.
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);
js calculator simple
js calculator simple

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

Javascript Calculator ideas (jssecrets.com)
Javascript Calculator ideas (jssecrets.com)

What to do next?

You can check some of my UI projects:

1️⃣ Javascript Custom Dropdown

2️⃣ Javascript Accordion

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.

Note
If you have some Javascript project in mind that you want me to explain, please write it in the comments.

Resources

1️⃣ jssecrets.com

2️⃣ Work illustrations by Storyset

3️⃣ People illustrations by Storyset

4️⃣ Education illustrations by Storyset

5️⃣ Win 95 CSS library

6️⃣ Javascript calculator at Codepen

Ilyas Seisov

Ilyas Seisov

UI/Web designer and Javascript developer with Master's degree in Computer Science. He helps businesses transform ideas into reality with the power of design and code. Ilyas creates modern, aesthetic web applications and reads minds in a free time.

Leave a Reply

Your email address will not be published. Required fields are marked *