Coded with HTML
, SCSS
, JavaScript
This is a solution to the Calculator app challenge on Frontend Mentor.
See Task.md for more details about the task.
Users should be able to:
- See the size of the elements adjust based on their device's screen size
- Perform mathmatical operations like addition, subtraction, multiplication, and division
- Adjust the color theme based on their preference
- Bonus: Have their initial theme preference checked using
prefers-color-scheme
and have any additional changes saved in the browser
- HTML5
- Semantic HTML
- SASS
- Flexbox
- Grid
- Responsive design
- Desktop first
- JavaScript
- JSDoc - just describe what functions do with JSDoc styled comments
- Github Pages - for hosting
- VS Code
- Live Server - VS Code extension that launches local servers
- Live Sass Complier - VS Code extension that transpiles SCSS/SASS files in CSS
Path | Description |
---|---|
public | Folder with .html and .css files |
public / index.html | Main HTML file |
public / index.css | Main CSS file, generated with SCSS transpiler |
public / index.css.map | Generated with SCSS transpiler, might be used for debugging, i guess |
src | Source files needed for application development |
src / assets | Images and other media used on the webpage |
src / index.scss | Main SCSS file, used to later generate CSS |
src / styles | Folder with SCSS partials, imported by index.scss |
src / index.js | Main file with JS |
src / scripts | Folder with JS functions imported by index.js |
docs | Folder with additional information, documentation |
docs / images | Folder with the application screenshots |
docs / images / goals | Folder with images from Frontendmentor that show how the page should look like |
docs / images / results | Folder with screenshots of how the application works after being fully developed |
docs / README-template.md | Template for README.md, don't use it (from FrontendMentor) |
docs / style-guide.md | Style information: color palette, fonts, etc. (from FrontendMentor) |
docs / task.md | Detailed task description (from FrontendMentor) |
I've been studying JS for the entire August 2022 3-4 hours per day from Monday to Sunday. By the start of the September 2022 I decided to code something challenging. The result is successful. The hardest part was to design the calculator logic. Learnt many things during the process of coding the logic, enjoyed it mostly.
Smooth, animated transition between 3 themes. A video from WebDev Simplified YouTube channel helped a lot to understand how it works.
Basically I just set the default variables for colors in body
selector in CSS, then redefine this variables in separate classes. Then bind with JS class addition and removal from <body>
.
Made toggle switch with a simple range <input>
. Styled it using this article from W3C. Also added animation on hover - the ball smoothly shrinks on hover, changes its color. Couldn't find a way to animate transition between value, although unsure if that's needed.
HTML code for the switcher
<input name="switcher" id="switcher" type="range" min="1" max="3" value="1" />
SCSS for selectors used to create themes
body {
--page-background-color: hsl(222, 26%, 31%);
--keypad-background-color: hsl(224, 36%, 15%);
--switcher-background-color: hsl(224, 36%, 15%);
--switcher-text-color: white;
// ...
}
.theme-1 {
--page-background-color: hsl(0, 0%, 90%);
--keypad-background-color: hsl(0, 5%, 81%);
--switcher-background-color: hsl(0, 5%, 81%);
--switcher-text-color: hsl(60, 10%, 19%);
// ...
}
.theme-2 {
--page-background-color: hsl(268, 75%, 9%);
--keypad-background-color: hsl(268, 71%, 12%);
--switcher-background-color: hsl(268, 71%, 12%);
--switcher-text-color: hsl(52, 100%, 62%);
// ...
}
SCSS for the switcher
#switcher {
appearance: none;
width: 4.5rem; // 72px
height: 1.5rem; // 24px
background: var(--switcher-background-color);
outline: none;
transition: 0.3s;
border-radius: 1rem;
padding: 0.5rem;
cursor: pointer;
// Yeah, they share the same styles, but
// "selector 1, selector 2" just doesn't properly work in this case
@mixin slider {
width: 1rem;
height: 1rem;
background: var(--button-accent-background-color);
border-radius: 1rem;
transition: 0.3s;
}
&::-webkit-slider-thumb {
@include slider;
appearance: none;
}
&::-moz-range-thumb {
@include slider;
}
&:hover::-webkit-slider-thumb {
background: var(--button-accent-background-hover);
transform: scale(0.85);
}
&:hover::-moz-range-thumb {
background: var(--button-accent-background-hover);
transform: scale(0.85);
}
}
Switcher logic in JS
// index.js
import { switcherHandler } from "./scripts/switcher.js";
document.querySelector("#switcher").addEventListener("change", switcherHandler);
// switcher.js
const switcherElement = document.querySelector("#switcher");
const themes = ["theme-1", "theme-2"];
/**
* Changes the page theme depending on value of range input.
* In order to change the theme the code adds or removes specific classes from `body` tag.
*
* @return {undefined} `undefined`
*/
export function switcherHandler() {
document.body.classList.remove(...themes);
switch (+switcherElement.value) {
case 1: // default theme
break;
case 2:
document.body.classList.add(themes[0]);
break;
case 3:
document.body.classList.add(themes[1]);
break;
default:
break;
}
return;
}
I use a free version of FrontendMentor. Thus I don't have access to Figma prototypes. But that's not a problem - I made my own one
Here it is: https://www.figma.com/file/HXUvWZAAVbKjeRxZtV97FT/Calculator-App?node-id=0%3A1
That helped me to make the calculator as close to the photos as possible
I bound pressing keys and pressing buttons
JS code
// index.js
import { pressButtonHandler } from "./scripts/pressButtons.js";
document.addEventListener("keydown", pressButtonHandler);
// pressButtons.js
export function pressButtonHandler(e) {
e.preventDefault(); // Otherwise pressing 'Enter' launches 2 events in a row
switch (e.key) {
// 1-ST ROW
case "7":
launchEventFromKeyboard("#num-7");
break;
case "8":
launchEventFromKeyboard("#num-8");
break;
case "9":
launchEventFromKeyboard("#num-9");
break;
case "Backspace":
launchEventFromKeyboard("#del");
break;
// 2-ND ROW
case "4":
launchEventFromKeyboard("#num-4");
break;
case "5":
launchEventFromKeyboard("#num-5");
break;
case "6":
launchEventFromKeyboard("#num-6");
break;
case "+":
launchEventFromKeyboard("#plus");
break;
// 3-RD ROW
case "1":
launchEventFromKeyboard("#num-1");
break;
case "2":
launchEventFromKeyboard("#num-2");
break;
case "3":
launchEventFromKeyboard("#num-3");
break;
case "-":
launchEventFromKeyboard("#minus");
break;
// 4-TH ROW
case ",":
case ".":
launchEventFromKeyboard("#dot");
break;
case "0":
launchEventFromKeyboard("#num-0");
break;
case "/":
launchEventFromKeyboard("#divide");
break;
case "x":
case "*":
launchEventFromKeyboard("#times");
break;
// 5-TH ROW
case "Delete":
launchEventFromKeyboard("#reset");
break;
case "=":
// case "Enter":
launchEventFromKeyboard("#equals");
break;
case "Enter":
launchEventFromKeyboard("#equals");
break;
}
return;
}
That's the most complex part, that probably requires length explanation... Although I'd rather recommend to read the code. I supplied it with comments. good luck
The logic is described mainly in index.js
, stackOperation.js
Used JS Doc notation (that is supported by VS Code by default) to explain how each function works.
Here are some examples:
Format number
/**
* Divides the integer in groups of three and adds space between group.
* Adds space after "-".
* Example: `-12312.123123` -> `- 12 312.123123`.
* Made to give the numbers a fancier look.
*
* @param {number | string} inputNumber
* @return {string} The formatted number
*/
export function numberWithDelimeters(inputNumber) {
let parts = inputNumber.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, " ");
parts[0] = parts[0].replace("-", "- ");
return parts.join(".");
}
Highlight operations
/**
* Highlights the element, removes highlight from previously higlighted element (if any)
* Selector for the highlighted element is stored in a const external array
* Highlight means to add a class with styles to that element
*
* @param {string} elementSelector - CSS selector of the element to be highlighted
* @return {undefined} undefined
*/
export function hightlightElement(elementSelector) {
if (highlightedElement.length > 0) {
document
.querySelector(highlightedElement.pop())
.classList.remove("highlight");
}
highlightedElement.push(elementSelector);
document.querySelector(elementSelector).classList.add("highlight");
return;
}
/**
* Removes highlight from the element with selector saved in a const external array
*
* @return {undefined} undefined
*/
export function removeHighlightFromElement() {
if (highlightedElement.length === 0) return;
document
.querySelector(highlightedElement.pop())
.classList.remove("highlight");
return;
}
This time made just 2 files: index.scss
, _globals.scss
. Nesting of CSS selectors helps a lot! Decided not to use BEM this time. Didn't see much difference. HTML looks cleaner, but I faced some issues because of using id in HTML tags.
- Light, Dark theme : Web Dev Simplified
- Custom range input sliders : W3C
- How to add transition to details tag : Stack Overflow
- How to format a number so it contained a delimiter after every 3 digits : Stack Overflow
- Live server : VS Code extension
- Live SASS Complier : VS Code extension
- Frontend Mentor - @GrbnvAlex
- Telegram - @Arlagonix
- Github - @arlagonix