HTML: (index.html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Calculator</title>
<link
href="https://fonts.googleapis.com/css?family=Roboto:300,400,700"
rel="stylesheet"
/>
<link href="css/normalize.css" rel="stylesheet" />
<link href="css/reset.css" rel="stylesheet" />
<link href="css/styles.css" rel="stylesheet" />
</head>
<body>
<div class="container">
<div class="calculator">
<div class="calculator__display">0</div>
<div class="calculator__keys">
<button data-key="plus" data-type="operator">+</button>
<button data-key="minus" data-type="operator">-</button>
<button data-key="times" data-type="operator">×</button>
<button data-key="divide" data-type="operator">÷</button>
<button data-key="1" data-type="number">1</button>
<button data-key="2" data-type="number">2</button>
<button data-key="3" data-type="number">3</button>
<button data-key="4" data-type="number">4</button>
<button data-key="5" data-type="number">5</button>
<button data-key="6" data-type="number">6</button>
<button data-key="7" data-type="number">7</button>
<button data-key="8" data-type="number">8</button>
<button data-key="9" data-type="number">9</button>
<button data-key="0" data-type="number">0</button>
<button class="decimal" data-type="number">.</button>
<button class="clear" data-type="clear">C</button>
<button data-key="equal" data-type="equal">=</button>
</div>
</div>
</div>
<script src="js/main.js"></script>
</body>
</html>
CSS: (normalize.css)
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */
/* Document
========================================================================== */
/**
* 1. Correct the line height in all browsers.
* 2. Prevent adjustments of font size after orientation changes in
* IE on Windows Phone and in iOS.
*/
html {
line-height: 1.15; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/* Sections
========================================================================== */
/**
* Remove the margin in all browsers (opinionated).
*/
body {
margin: 0;
}
/**
* Add the correct display in IE 9-.
*/
article,
aside,
footer,
header,
nav,
section {
display: block;
}
/**
* Correct the font size and margin on `h1` elements within `section` and
* `article` contexts in Chrome, Firefox, and Safari.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/* Grouping content
========================================================================== */
/**
* Add the correct display in IE 9-.
* 1. Add the correct display in IE.
*/
figcaption,
figure,
main { /* 1 */
display: block;
}
/**
* Add the correct margin in IE 8.
*/
figure {
margin: 1em 40px;
}
/**
* 1. Add the correct box sizing in Firefox.
* 2. Show the overflow in Edge and IE.
*/
hr {
box-sizing: content-box; /* 1 */
height: 0; /* 1 */
overflow: visible; /* 2 */
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
pre {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/* Text-level semantics
========================================================================== */
/**
* 1. Remove the gray background on active links in IE 10.
* 2. Remove gaps in links underline in iOS 8+ and Safari 8+.
*/
a {
background-color: transparent; /* 1 */
-webkit-text-decoration-skip: objects; /* 2 */
}
/**
* 1. Remove the bottom border in Chrome 57- and Firefox 39-.
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
*/
abbr[title] {
border-bottom: none; /* 1 */
text-decoration: underline; /* 2 */
text-decoration: underline dotted; /* 2 */
}
/**
* Prevent the duplicate application of `bolder` by the next rule in Safari 6.
*/
b,
strong {
font-weight: inherit;
}
/**
* Add the correct font weight in Chrome, Edge, and Safari.
*/
b,
strong {
font-weight: bolder;
}
/**
* 1. Correct the inheritance and scaling of font size in all browsers.
* 2. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp {
font-family: monospace, monospace; /* 1 */
font-size: 1em; /* 2 */
}
/**
* Add the correct font style in Android 4.3-.
*/
dfn {
font-style: italic;
}
/**
* Add the correct background and color in IE 9-.
*/
mark {
background-color: #ff0;
color: #000;
}
/**
* Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` elements from affecting the line height in
* all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/* Embedded content
========================================================================== */
/**
* Add the correct display in IE 9-.
*/
audio,
video {
display: inline-block;
}
/**
* Add the correct display in iOS 4-7.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Remove the border on images inside links in IE 10-.
*/
img {
border-style: none;
}
/**
* Hide the overflow in IE.
*/
svg:not(:root) {
overflow: hidden;
}
/* Forms
========================================================================== */
/**
* 1. Change the font styles in all browsers (opinionated).
* 2. Remove the margin in Firefox and Safari.
*/
button,
input,
optgroup,
select,
textarea {
font-family: sans-serif; /* 1 */
font-size: 100%; /* 1 */
line-height: 1.15; /* 1 */
margin: 0; /* 2 */
}
/**
* Show the overflow in IE.
* 1. Show the overflow in Edge.
*/
button,
input { /* 1 */
overflow: visible;
}
/**
* Remove the inheritance of text transform in Edge, Firefox, and IE.
* 1. Remove the inheritance of text transform in Firefox.
*/
button,
select { /* 1 */
text-transform: none;
}
/**
* 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`
* controls in Android 4.
* 2. Correct the inability to style clickable types in iOS and Safari.
*/
button,
html [type="button"], /* 1 */
[type="reset"],
[type="submit"] {
-webkit-appearance: button; /* 2 */
}
/**
* Remove the inner border and padding in Firefox.
*/
button::-moz-focus-inner,
[type="button"]::-moz-focus-inner,
[type="reset"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
border-style: none;
padding: 0;
}
/**
* Restore the focus styles unset by the previous rule.
*/
button:-moz-focusring,
[type="button"]:-moz-focusring,
[type="reset"]:-moz-focusring,
[type="submit"]:-moz-focusring {
outline: 1px dotted ButtonText;
}
/**
* Correct the padding in Firefox.
*/
fieldset {
padding: 0.35em 0.75em 0.625em;
}
/**
* 1. Correct the text wrapping in Edge and IE.
* 2. Correct the color inheritance from `fieldset` elements in IE.
* 3. Remove the padding so developers are not caught out when they zero out
* `fieldset` elements in all browsers.
*/
legend {
box-sizing: border-box; /* 1 */
color: inherit; /* 2 */
display: table; /* 1 */
max-width: 100%; /* 1 */
padding: 0; /* 3 */
white-space: normal; /* 1 */
}
/**
* 1. Add the correct display in IE 9-.
* 2. Add the correct vertical alignment in Chrome, Firefox, and Opera.
*/
progress {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}
/**
* Remove the default vertical scrollbar in IE.
*/
textarea {
overflow: auto;
}
/**
* 1. Add the correct box sizing in IE 10-.
* 2. Remove the padding in IE 10-.
*/
[type="checkbox"],
[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Correct the cursor style of increment and decrement buttons in Chrome.
*/
[type="number"]::-webkit-inner-spin-button,
[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Correct the odd appearance in Chrome and Safari.
* 2. Correct the outline style in Safari.
*/
[type="search"] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/**
* Remove the inner padding and cancel buttons in Chrome and Safari on macOS.
*/
[type="search"]::-webkit-search-cancel-button,
[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* 1. Correct the inability to style clickable types in iOS and Safari.
* 2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/* Interactive
========================================================================== */
/*
* Add the correct display in IE 9-.
* 1. Add the correct display in Edge, IE, and Firefox.
*/
details, /* 1 */
menu {
display: block;
}
/*
* Add the correct display in all browsers.
*/
summary {
display: list-item;
}
/* Scripting
========================================================================== */
/**
* Add the correct display in IE 9-.
*/
canvas {
display: inline-block;
}
/**
* Add the correct display in IE.
*/
template {
display: none;
}
/* Hidden
========================================================================== */
/**
* Add the correct display in IE 10-.
*/
[hidden] {
display: none;
}
CSS: (reset.css)
*******************
Box Sizing
*******************/
html {
box-sizing: border-box;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
/****************************
Generic Margins and Paddings
****************************/
body,
h1,
h2,
h3,
h4,
h5,
h6,
ul,
ol,
li,
p,
pre,
blockquote,
figure,
hr {
margin: 0;
padding: 0;
}
/*******************
Lists
*******************/
ul {
list-style: none;
}
/*******************
Forms and buttons
*******************/
input,
textarea,
select,
button {
color: inherit;
font: inherit;
letter-spacing: inherit;
}
/* I usually expand input and textarea to full-width */
input[type="text"],
textarea {
width: 100%;
}
/* More friendly border */
input,
textarea,
button {
border: 1px solid gray;
}
/* Some defaults for one-liner buttons */
button {
padding: 0.75em 1em;
border-radius: 0;
line-height: 1;
background-color: transparent;
}
button * {
pointer-events: none;
}
/***********************************
Easy responsive for media elements
***********************************/
img,
svg,
iframe,
video,
object,
embed {
display: block;
max-width: 100%;
}
/*******************
Useful table styles
*******************/
table {
table-layout: fixed;
width: 100%;
}
/*******************
The hidden attribute
*******************/
[hidden] {
display: none !important;
}
/*******************
Noscript
*******************/
noscript {
display: block;
margin-bottom: 1em;
margin-top: 1em;
}
/*******************
Focus
*******************/
[tabindex="-1"] {
outline: none !important;
}
CSS: (styles.css)
/**
* base styles and typography
* ---------------
*/
:root {
--orange-050: #ffe8d9;
--orange-100: #ffd0b5;
--orange-200: #ffb088;
--orange-300: #ff9466;
--orange-400: #f9703e;
--orange-500: #f35627;
--orange-600: #de3a11;
--orange-700: #c52707;
--orange-800: #ad1d07;
--orange-900: #841003;
--grey-050: #f5f7fa;
--grey-100: #e4e7eb;
--grey-200: #cbd2d9;
--grey-300: #9aa5b1;
--grey-400: #7b8794;
--grey-500: #616e7c;
--grey-600: #52606d;
--grey-700: #3e4c59;
--grey-800: #323f4b;
--grey-900: #1f2933;
}
/*******************
Base styles
*******************/
html {
font-size: 150%;
font-weight: 300;
font-family: "Roboto", Helvetica, Arial, sans-serif;
line-height: 1.4;
}
body {
display: flex;
background-image: linear-gradient(236deg, #74ebd5, #acb6e5);
justify-content: center;
align-items: center;
height: 100vh;
}
/*******************
Calculator
*******************/
.calculator {
box-shadow: 0 0 25px 0 rgba(0, 0, 0, 0.3);
border-radius: 0.5em;
overflow: hidden;
}
.calculator__display {
background-color: var(--grey-900);
color: var(--grey-050);
width: 16.25rem;
padding: 0.75rem 1rem;
text-align: right;
font-size: calc(1rem * 1.2 * 1.2);
}
.calculator__keys {
background: var(--grey-200);
display: grid;
grid-gap: 2px;
grid-template-columns: repeat(4, auto);
grid-template-areas:
". . . ."
"seven eight nine equal"
"four five six equal"
"one two three equal"
"zero decimal clear equal";
}
.calculator__keys > button {
border: 0;
}
[data-key="1"] {
grid-area: one;
}
[data-key="2"] {
grid-area: two;
}
[data-key="3"] {
grid-area: three;
}
[data-key="4"] {
grid-area: four;
}
[data-key="5"] {
grid-area: five;
}
[data-key="6"] {
grid-area: six;
}
[data-key="7"] {
grid-area: seven;
}
[data-key="8"] {
grid-area: eight;
}
[data-key="9"] {
grid-area: nine;
}
[data-key="0"] {
grid-area: zero;
}
.decimal {
grid-area: decimal;
}
.clear {
grid-area: clear;
}
[data-key="equal"] {
grid-area: equal;
background-color: var(--orange-400);
}
[data-key="equal"]:active {
background-color: var(--orange-500);
}
[data-type="operator"] {
background: var(--grey-100);
}
[data-type="number"],
[data-type="clear"] {
background: #fff;
}
[data-type="operator"]:active,
[data-type="number"]:active,
[data-type="clear"]:active {
background: var(--grey-200);
}
[data-state="selected"] {
background: var(--grey-200);
}
JavaScript (main.js)
// Start writing JavaScript here!
const calculator = document.querySelector('.calculator')
const keys = calculator.querySelector('.calculator__keys')
const display = calculator.querySelector('.calculator__display')
const operatorKeys = keys.querySelectorAll('[data-type="operator"]')
keys.addEventListener('click', event => {
if (!event.target.closest('button')) return
const key = event.target
const keyValue = key.textContent
const displayValue = display.textContent
const { type } = key.dataset
const { previousKeyType } = calculator.dataset
if (type === 'number') {
if (
displayValue === '0' ||
previousKeyType === 'operator'
) {
display.textContent = keyValue
} else {
display.textContent = displayValue + keyValue
}
}
if (type === 'operator') {
operatorKeys.forEach(el => { el.dataset.state = '' })
key.dataset.state = 'selected'
calculator.dataset.firstNumber = displayValue
calculator.dataset.operator = key.dataset.key
}
if (type === 'equal') {
// Perform a calculation
const firstNumber = calculator.dataset.firstNumber
const operator = calculator.dataset.operator
const secondNumber = displayValue
display.textContent = calculate(firstNumber, operator, secondNumber)
}
if (type === 'clear') {
display.textContent = '0'
delete calculator.dataset.firstNumber
delete calculator.dataset.operator
}
calculator.dataset.previousKeyType = type
})
function calculate (firstNumber, operator, secondNumber) {
firstNumber = parseInt(firstNumber)
secondNumber = parseInt(secondNumber)
if (operator === 'plus') return firstNumber + secondNumber
if (operator === 'minus') return firstNumber - secondNumber
if (operator === 'times') return firstNumber * secondNumber
if (operator === 'divide') return firstNumber / secondNumber
}
// ========================
// TESTING
// ========================
function clearCalculator () {
// Press the clear key
const clearKey = document.querySelector('[data-type="clear"]')
clearKey.click()
// Clear operator states
operatorKeys.forEach(key => { key.dataset.state = '' })
}
function testClearKey () {
clearCalculator()
console.assert(display.textContent === '0', 'Clear key. Display should be 0')
console.assert(!calculator.dataset.firstNumber, 'Clear key. No first number remains')
console.assert(!calculator.dataset.operator, 'Clear key. No operator remains')
}
function testKeySequence (test) {
// Press keys
test.keys.forEach(key => {
document.querySelector(`[data-key="${key}"]`).click()
})
// Assertion
console.assert(display.textContent === test.value, test.message)
// Clear calculator
clearCalculator()
testClearKey()
}
const tests = [{
keys: ['1'],
value: '1',
message: 'Click 1'
}, {
keys: ['1', '5'],
value: '15',
message: 'Click 15'
}, {
keys: ['1', '5', '9'],
value: '159',
message: 'Click 159'
}, {
keys: ['2', '4', 'plus', '7', 'equal'],
value: '31',
message: 'Calculation with plus'
}, {
keys: ['3', 'minus', '7', '0', 'equal'],
value: '-67',
message: 'Calculation with minus'
}, {
keys: ['1', '5', 'times', '9', 'equal'],
value: '135',
message: 'Calculation with times'
}, {
keys: ['9', 'divide', '3', 'equal'],
value: '3',
message: 'Calculation with divide'
}, {
keys: ['9', 'divide', '0', 'equal'],
value: 'Infinity',
message: 'Calculation. Divide by 0'
}]
tests.forEach(testKeySequence)
Download Code | created by Kevin Powell
Comments
Post a Comment