
학원에서 자바스크립트 기초 과정을 마친 후, 4일간 진행된 계산기 만들기 프로젝트를 통해 실제로 JavaScript를 활용해보는 시간을 가졌다. 계산기 구현이 간단할 줄 알았지만, 경우의 수가 많고 생각보다 까다로웠다. 특히 배운 지 3주밖에 안 된 상황에서 코드를 직접 작성하는 건 꽤 어려운 일이었다.
HTML
<div class="display">0</div> <div class="buttons"> <button class="button function">C</button> <button class="button function">±</button> <button class="button function">%</button> <button class="button operator">/</button> <button class="button number">7</button> <button class="button number">8</button> <button class="button number">9</button> <button class="button operator">*</button> <button class="button number">4</button> <button class="button number">5</button> <button class="button number">6</button> <button class="button operator">-</button> <button class="button number">1</button> <button class="button number">2</button> <button class="button number">3</button> <button class="button operator">+</button> <button class="button number zero">0</button> <button class="button"><span class="dot-text">.</span></button> <button class="button">=</button> </div> </div>
CSS
.buttons { height: 430px; color: black; padding: 0px; display: flex; flex-wrap: wrap;/*자식요소 줄바꿈*/ gap: 8px; } .button { width: 23.3%; height: 18%; font-size: 28px; border-radius: 50px; border: none; background-color: white; cursor: pointer; } .button:nth-child(1), .button:nth-child(2), .button:nth-child(3) { background-color: #dcdcde; color: #454242; } .button:nth-child(4n) { background-color: #bebec1; color: #272727; } .button:nth-child(8) { padding-top: 15px; } .button:nth-child(19) { background-color: #fb8181; color: #3b3b3b; } .button:hover { background-color: #d7e9fc; } .button:active { background-color: #7bb1f4; box-shadow: 7px 7px 20px #7b96ad; } .zero { width: 48.5%; border-radius: 35px; font-size: 28px; }
학원에서는 계산기 만들기 프로젝트를 총 5단계로 나누어 진행했다. 1, 2단계는 HTML과 CSS 구현, 3, 4단계는 JavaScript 기능 구현, 마지막 5단계는 완성된 계산기를 배포하는 단계였다.
팀원 각자가 계산기를 구현하며, 각 단계마다 자신의 코드를 설명하고 조교님의 질문에 통과하면 다음 단계로 넘어갈 수 있는 구조였다. 덕분에 기능 구현뿐 아니라 코드를 말로 설명하는 연습까지 병행할 수 있었던 경험이었다.
처음 1, 2단계를 진행할 때는 기본적인 구조만 구현했지만, 점차 진행하면서 CSS 디테일에도 신경을 쓰게 되었다. 버튼들을 하나의 div 안에 구성하다 보니 줄바꿈 처리가 필요했고, 나는 .buttons 클래스에 display: flex와 flex-wrap: wrap 속성을 사용했다.
nowrap은 한 줄에 무조건 배치되지만, wrap은 요소가 넘칠 경우 줄바꿈을 해준다.
줄바꿈 구현은 grid 방식이나 버튼 줄마다 div를 나누는 방식도 있었지만, 학원에서는 flexbox를 활용하라고 하셔서 그 방법을 선택했다. 아직 grid는 배운 적이 없지만, 나중에 꼭 공부해보고 싶다.
버튼 크기는 처음에는 가로 4개, 세로 5개 구성으로 가정하고 가로는 25%, 세로는 20%로 지정했었다. 0 버튼만 2배 크기로 지정해야 했기에 가로 50%를 할당했는데, 이후 전체 크기 조절과 gap 추가로 인해 비율이 다소 어긋나는 부분이 생겼다.
CSS 계산을 너무 꼼꼼히 하다 보면 개발이 느려질 수 있어, 이번에는 눈으로 봤을 때 자연스러운 정도로 비율을 조정했다.
배경에는 그라데이션 효과를 주고 싶어서 CSS Gradient 사이트를 활용했다. 하지만 색 조합을 고르기가 어려워 Pinterest에서 컬러 조합을 참고해 연보라와 분홍 계열을 사용했다. 버튼 색상도 Pinterest에서 계산기 UI를 검색해보고 적당한 조합을 선택했다.
JavaScript
//3-2단계 : 숫자를 디스플레이에 표시하기 //3-2-1. 모든 버튼 요소와 디스플레이 요소를 선택합니다. //모든 버튼 요소를 선택 const buttons = document.querySelectorAll('.button'); //디스플레이 요소를 선택 const display = document.querySelector('.display'); //3-2-2. 각 버튼에 클릭 이벤트 리스너를 추가합니다. buttons.forEach(function(btn){ btn.addEventListener('click', function(e){ const value = e.target.textContent; //각 버튼을 클릭했을 때 console에 각 버튼의 value가 나오도록 하기 (3-1단계) console.log(value); //3-2-3. 버튼이 클릭되었을 때, 클래스가 `number`인 경우 디스플레이에 값을 표시한다. if (btn.classList.contains('number')){ //디스플레이 화면에 숫자가 10자를 초과한 경우 더 이상 입력을 제한한다. if (display.textContent.length >= 10) { alert('숫자를 더 이상 입력할 수 없습니다.') return } //3-2-4. 디스플레이가 `0`인 경우 if (display.textContent === '0') { //클릭한 숫자 값으로 바뀌어야 한다. display.textContent = value; } //3-2-5. 디스플레이가 `0`이 아닌 그 밖의 경우 else { //클릭한 숫자를 뒤에 추가해준다. display.textContent += value; } } //3단계 추가 기능 1번) 소수점(.) 버튼을 클릭하면 디스플레이에 소수점을 추가하세요. (이미 소수점이 있는 경우 추가되지 않도록) else if (value === '.') { //이미 소수점이 있지 않으면 if (!display.textContent.includes('.')){ //디스플레이에 소수점을 추가한다. display.textContent += '.'; } } //3단계 추가 기능 2번) C 버튼을 클릭하면 디스플레이를 0으로 초기화하세요. else if (value === 'C') { display.textContent = '0'; //첫 번째 피연산자와 연산자는 초기화를 해준다. firstOperand = null; operator = null; }
3단계부터는 자바스크립트로 기능을 구현해야 했는데, 처음엔 막막했다. DOM을 직접 다뤄본 경험이 없다 보니 어디서부터 시작해야 할지 감이 안 잡혔다.
그럴 땐 검색이 답이었다. 구글과 유튜브는 물론, 이해가 잘 되지 않는 부분은 챗GPT에게 질문하기도 하며 하나씩 해결해나갔다. 지금 다시 보면 간단해 보이는 코드들도 당시엔 낯설고 어렵게 느껴졌다.
특히 그동안 글로만 학습했던 const, let, null의 차이를 이번 프로젝트를 통해 실감할 수 있었다. 값이 바뀌지 않는 건 const, 변화할 수 있는 건 let, 초기값을 비워두려면 null을 사용하는 이유가 실전에서 자연스럽게 체득되었다.
또한 조교님이 팀 미션 중 forEach와 map의 차이를 질문하신 적이 있었는데, 이 질문 덕분에 팀원들과 함께 해당 개념을 다시 공부해볼 수 있었다.
forEach()는 단순 반복 작업에 사용되고, map()은 원본 배열을 기반으로 새로운 배열을 반환할 때 사용된다.
map()은 반드시 값을 return하거나 변수에 할당해야 의미가 있다. 그 차이를 확실히 이해하고 나니, 앞으로 어떤 상황에 어떤 메서드를 써야 할지 기준이 생겼다.
JavaScript
//4-1단계: 디스플레이에 숫자를 입력한 다음 연산기호를 누르면 디스플레이에 있는 숫자를 `firstOperand`로 저장하고 연산기호를 기억하기 //4-1-1. firstOperand, operator 변수를 선언합니다. let firstOperand = null; let operator = null; // 4-2단계: `calculate` 함수 구현 및 `=` 버튼 클릭 시 계산 수행 // 4-2-1. `calculate` 함수를 구현합니다. function calculate(num1, operator, num2){ // 매개변수로 받은 입력 값을 숫자로 변환해줍니다. num1 = Number(num1); num2 = Number(num2); switch (operator) { case '+' : return num1 + num2; case '-' : return num1 - num2; case '*' : return num1 * num2; case '/' : if (num2 === 0) { alert('0으로 나눌 수 없습니다.'); return 0; } return num1 / num2; default: return num2; // operator가 없는 경우 두 번째 숫자로 그대로 반환해줍니다. } }
4단계에 들어서면서는 문자열로 입력된 값을 숫자로 변환해야 하는 상황이 많아졌다. 숫자 변환에는 parseInt, parseFloat, Number, +문자열 등 다양한 방법이 있는데, 나는 그중에서 Number를 선택했다. 이유는 단순하게 (.)소수점도 지원하고, 코드도 직관적으로 보였기 때문이다.
그리고 calculate 함수를 작성할 때는 처음에는 if-else 문으로 구성하려 했지만, 조건이 많아질수록 가독성이 떨어지는 느낌이 들어 switch문을 사용하기로 했다. 학원에서 과제로 이미 switch를 다뤄본 적이 있었기 때문에 조금은 익숙하기도 했다.
처음에는 case마다 break를 써서 구현했지만, 작동이 잘 안 되는 문제가 발생했다. 알고 보니 break는 단순히 블록을 빠져나가는 역할이어서, 반환값이 필요한 경우에는 별도로 변수를 따로 선언해서 저장해야 했다. 이 과정을 단순화하기 위해 return을 사용해 바로 값을 반환하는 방식으로 수정했고, 그 이후에는 문제없이 작동했다.
JavaScript
// 4-1-2. 연산기호 버튼이 클릭되면 현재 디스플레이 값을 `firstOperand`로 저장하고, 연산기호를 기억합니다. //클릭한 버튼이 연산자일 경우, else if (btn.classList.contains('operator')) { // 첫 번째 피연산자가 null이면 현재 디스플레이 값을 firstOperand로 저장합니다. if(firstOperand === null) { firstOperand = display.textContent; } // operator 변수에 클릭한 연산기호를 값으로 할당합니다. operator = value; // firstOperand와 operator를 console에 출력합니다. console.log('First Operand:', firstOperand); console.log('Operator:', operator); // 4-1-3. 연산기호 버튼이 클릭된 후 디스플레이에 다른 숫자를 입력하면 새로운 숫자가 디스플레이에 입력되도록 합니다. // 연산기호 버튼이 클릭된 후 두 번째 숫자를 입력하면 디스플레이의 값이 새로 입력한 숫자로 바뀝니다. display.textContent = ''; } // 4-2-2. `=` 버튼이 클릭되면 `firstOperand`, `operator`, `secondOperand`를 전달하여 계산을 수행하고 결과를 디스플레이에 표시합니다. else if (value === '=') { if (firstOperand !== null && operator !== null) { //디스플레이 화면에 표시되는 숫자를 두 번째 피연산자에 할당해준다. const secondOperand = display.textContent; //두번째 피연산자 secondOperand도 출력합니다. console.log(`Second Operand:`, secondOperand) //calculate 함수를 결과 값에 할당해줍니다. const total = calculate(firstOperand, operator, secondOperand); //디스플레이 화면에 결과 값을 보여줍니다. (소수점은 6자리까지만) display.textContent = Number(total.toFixed(6)); //첫 번째 피연산자와 연산자는 초기화를 해준다. firstOperand = null; operator = null; } }
계산기의 결과값을 디스플레이에 보여줄 때는 처음에 display.textContent = total;로 작성했다. 하지만 나눗셈 결과처럼 소수점 이하 자릿수가 긴 경우, 디스플레이 영역을 초과해 숫자가 잘려 보이지 않는 문제가 발생했다.
실시간 시간 강의 때 강사님이 toFixed() 메서드를 활용하면 된다고 알려주셔서, display.textContent = Number(total.toFixed(6)); 형태로 코드를 수정했다. 이렇게 하면 소수점 아래 6자리까지만 표시되어 시각적으로 깔끔하게 제한할 수 있었다.
toFixed는 참고로 숫자를 문자열로 형태로 반환해주는 메서드이기 때문에 다시 Number로 감싸주어야 일반적인 숫자 형태로 사용하거나 표시 할 수 있다.
JavaScript
// 계산기 전체를 선택합니다. const calculator = document.querySelector('.calculator-container'); // 닫기버튼에 빨간색 버튼을 지정합니다. const closeBtn = document.querySelector('.red-button'); // 다시 계산기를 보여주는 버튼을 할당합니다. const reOpenBtn = document.querySelector('.reopen'); // 계산기 크기를 줄여주는 버튼을 노란색 버튼으로 지정합니다. const smallBtn = document.querySelector('.yellow-button'); // 계산기 크기를 키워주는 버튼을 초록색 버튼으로 지정합니다. const bigBtn = document.querySelector('.green-button'); // 계산기 크기를 원본으로 변경해주는 버튼을 회색 버튼으로 지정합니다. const originalBtn = document.querySelector('.gray-button') //빨간색 버튼을 클릭하면 계산기를 숨깁니다. closeBtn.addEventListener('click', function() { calculator.classList.add('hidden'); reOpenBtn.style.display = 'block'; }); //버튼을 클릭하면 숨겨진 계산기가 나타나고 버튼은 사라집니다. reOpenBtn.addEventListener('click', function() { calculator.classList.remove('hidden'); reOpenBtn.style.display = 'none'; }) //노란색 버튼을 클릭하면 계산기 크기를 살짝 줄여줍니다. smallBtn.addEventListener('click', function() { calculator.style.transform = 'scale(0.9)'; calculator.style.transition = 'transform 0.3s ease'; // 0.3초간 진행, 처음에 느리게 시작 -> 빠르게 -> 천천히 종료 }); //초록색 버튼을 클릭하면 계산기 크기를 살짝 키워줍니다. bigBtn.addEventListener('click', function() { calculator.style.transform = 'scale(1.1)'; calculator.style.transition = 'transform 0.3s ease'; // 0.3초간 진행, 처음에 느리게 시작 -> 빠르게 -> 천천히 종료 }); //회색 버튼을 클릭하면 계산기 크기를 원본으로 변경해줍니다. originalBtn.addEventListener('click', function() { calculator.style.transform = 'scale(1.0)'; calculator.style.transition = 'transform 0.3s ease'; // 0.3초간 진행, 처음에 느리게 시작 -> 빠르게 -> 천천히 종료 });
이 부분은 사실 학원 미션에는 없던 내용이지만, 왼쪽 상단의 색깔 버튼들이 단지 디자인 요소로만 있는 게 아쉬워 기능을 추가해보기로 했다.
초록색 버튼은 원래 맥북에서 창을 최대화하는 역할을 한다. 이를 구현하려다 보니 브라우저마다 처리 방식이 달라서 복잡했다. 그래서 기능을 간단히 바꾸어 노란색 버튼은 계산기를 작게, 초록색 버튼은 크게 조절하도록 구현했다. 이 작업은 JavaScript로 DOM에 접근해 style.transform만 설정해주면 되기 때문에 생각보다 수월했다.
기능을 추가하고 보니, 다시 원래 크기로 되돌리는 버튼이 있으면 좋겠다는 생각이 들었다. 그래서 오른쪽에 회색 버튼을 하나 더 만들고, 클릭 시 원래 크기 (scale(1.0))로 돌아가게 설정했다.
버튼을 색상만으로 구분하기보다는 안에 기호를 추가하면 더 직관적일 것 같아서 기호도 넣었다. 특히 회색과 노란색 버튼에 어떤 기호를 넣을지 고민이 많았는데, 이 부분은 챗GPT의 도움을 받아 깔끔한 기호를 찾을 수 있었다.
버튼 색상 역시 처음엔 ColorZilla 확장 프로그램으로 대충 따와서 썼지만, 회색 버튼까지 추가하면서 색 조합이 어색해 보여 GPT에게 추천을 받아 조정했고, 결과적으로 색상과 기능 모두 만족스러운 구성으로 완성할 수 있었다.

빨간색 버튼은 계산기 전체를 숨기는 기능을 하며, Calculator Open 버튼을 누르면 다시 계산기를 화면에 표시한다. 모달처럼 열고 닫는 인터랙션을 구현한 것이 포인트다.
버튼 디자인 참고에 도움이 되었던 사이트는 아래와 같다:
계산기 프로젝트를 마무리하면서 아쉬웠던 점도 몇 가지 있다:
1. 반응형 대응 부족: 모바일 화면에서는 계산기가 제대로 보이지 않고 버튼들이 줄바꿈되거나 잘리기도 했다. 반응형을 고려한 뷰포트 설정과 미디어 쿼리 적용이 필요함을 느꼈다.
2. toFixed()의 한계: 나눗셈 결과는 소수점 제한이 잘 적용되지만, 큰 수의 곱셈 결과는 디스플레이 영역을 넘어가 여전히 잘리는 현상이 있었다. 이 부분은 숫자 자릿수를 제한하거나 폰트 크기를 자동 조절하는 로직이 추가로 필요해 보인다.
3. 화살표 함수 사용 미흡: 이번 과제에서는 대부분 일반 함수 형식을 사용했는데, 앞으로는=>함수 표현식도 많이 활용해보고 익숙해져야겠다는 생각이 들었다.
처음에는 미션 요구사항 자체가 낯설고 어렵게 느껴졌지만, 단계별로 기능을 쪼개고 하나씩 구현하다 보니 어느새 하나의 프로그램을 완성할 수 있었다. 계산기 하나를 만들면서도 다양한 로직과 조건, 예외 처리들을 고려해야 했고, 그 과정을 통해 실력이 한층은 더 성장한 것 같다.
앞으로는 시간이 좀 더 지나면 팀 프로젝트도 경험하게 될 텐데, 그 전에 기초를 좀 더 탄탄히 다져야겠다는 다짐을 하며 이 프로젝트를 마무리해본다.