×자바스크립트로 계산기를 만들어보고자 했다!
만들기 위해 참고한 글은 이곳
https://aosceno.tistory.com/615
[JS] 바닐라 자바스크립트로 간단한 계산기 만들어보기(2)
개선 사항 : 좀 더 단순화한 data object +/- 음수 양수 변환 추가 연산자로 이용한 연속계산과, = 버튼을 이용한 반복계산 오류 안 나게 개선 eval이 아닌 switch문으로 변경 See the Pen Caculator v1.2 by Oh..
aosceno.tistory.com
해당 글을 참고하여 계산기를 만들었고 그 과정에서 찾은 오류들을 해결하여 최종 계산기를 만들었다.
해당 글에서 찾은 오류는
1. 여러 값을 연속으로 계산하는 경우 앞의 연산자가 아닌 뒤의 연산자를 기준으로 계산되어서
5+3+2-4는 6이 되어야 하는데 +2가 +2로 계산되지 않고 뒤에 누른 -연산자로 인해 -2로 계산되었다.
2. =버튼을 눌러서 결괏값을 얻고 양수/음수 변경을 하면 결괏값의 양수/음수가 변경되는 것이 아니라 이전에 입력했던 값이 변경되었다.
먼저 전체 코드와 구현은 이렇다.
See the Pen Untitled by JiYoung (@yjzero) on CodePen.
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.0">
<title>Document</title>
<link rel="stylesheet" href="style.css">
<script defer src="app.js"></script>
</head>
<body>
<div id="caculator">
<article id="area-display">
<div id="display" class="off">0</div>
<div id="steps">0</div>
</article>
<article id="area-btn">
<button class="num" data-val="7">7</button>
<button class="num" data-val="8">8</button>
<button class="num" data-val="9">9</button>
<button id="reset">C</button>
<button class="num" data-val="4">4</button>
<button class="num" data-val="5">5</button>
<button class="num" data-val="6">6</button>
<button class="op" data-val="/">÷</button>
<button class="num" data-val="1">1</button>
<button class="num" data-val="2">2</button>
<button class="num" data-val="3">3</button>
<button class="op" data-val="*">×</button>
<button class="num" id="interger" data-val="-1">+/-</button>
<button class="num" data-val="0">0</button>
<button class="op" data-val=".">.</button>
<button class="op" data-val="-">-</button>
<button id="btn_result" >=</button>
<button class="op" data-val="+">+</button>
</article>
</div>
</body>
</html>
html은 button 태그를 이용하여 구조를 만들었고 해당 버튼의 숫자와 연산자에 따라 data-val 값을 갖도록 하였다.
CSS
*{
padding: 0;
margin: 0;
box-sizing: border-box;
}
body{
background-color: #707070;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
button {
border-radius: 20%;
border: solid 1px rgb(197, 167, 167);
cursor: pointer;
}
button:hover {
box-shadow: 1px 1px 1px 1px rgba(209, 209, 209, 0.7);
}
#caculator{
background-color: #ffd8d6;
display: flex;
flex-direction: column;
width: 297px;
border: 1px solid #943134;
}
#area-display{
background-color: #ffe9e8;
display: flex;
flex-direction: column;
text-align: right;
height: 90px;
padding: 10px 10px;
}
#area-display #steps{
font-size: 25px;
word-break: break-all;
}
#area-display #display{
color: #707070;
}
#area-btn{
display: grid;
grid-gap: 5px;
grid-template-columns: repeat(4, 65px);
grid-auto-rows: 65px;
padding: 10px;
}
#area-btn #btn_result{
grid-column: span 3;
}
.num{
background-color:#fffde7;
}
.num:hover {
background-color: #fffccd;
}
.op, #interger, #reset, #btn_result {
background-color: #ffebee;
}
.op:hover, #interger:hover, #reset:hover, #btn_result:hover {
background-color: #ffd0d7;
}
.off{
visibility: hidden;
}
css는 기본 틀을 grid를 이용해서 짰다.
JavaScript
const $steps = document.querySelector('#steps');
const $display = document.querySelector('#display');
const $area_btn = document.querySelector('#area-btn');
const data = {
prev : '',
curr : '',
operator: undefined
}
// 숫자 버튼을 누르는 경우
function on_num(operator, target) {
let prevOrcurr = operator ? 'curr' : 'prev';
const val = target.dataset.val;
if(val === "-1") {
if(data.curr !== '') {
data[prevOrcurr] = Number(data.curr)*-1 ;
} else{
data[[prevOrcurr]] = Number(data.prev)*-1 ;
}
} else{
data[prevOrcurr] += val;
}
$steps.innerHTML = data[prevOrcurr];
}
//연산자 버튼을 누른 경우
function on_ops(target){
$display.classList.remove('off');
on_result();
const dataOp = target.dataset.val;
data.operator = dataOp;
if(data.prev === undefined) return;
show_middleStep();
}
//연산자 버튼을 누른 경우 계산
function on_result(){
if(data.prev === undefined || data.curr === undefined || data.operator === undefined) return;
data.prev = cal_sum();
$display.innerHTML = data.prev;
data.curr='';
}
//= 버튼을 누른 경우
function show_result(){
if(data.prev === undefined || data.curr === undefined || data.operator === undefined) return;
$display.classList.add('off');
data.prev = cal_sum();
$steps.innerHTML = data.prev;
data.curr='';
}
//값 계산
function cal_sum(){
const {prev,curr,operator} = data;
switch(operator){
case "+":
return Number(prev) + Number(curr);
case "-":
return Number(prev) - Number(curr);
case "*":
return Number(prev) * Number(curr);
case "/":
return Number(prev) / Number(curr);
}
}
//연산자 문자열로 변환
function operator_to_String(){
const {operator} = data;
switch (operator){
case "+" :
return "+";
case "-" :
return "-";
case "*" :
return "×";
case "/" :
return "÷";
}
}
//계산과정 출력하기
function show_middleStep(){
const middleSteps = `${data.prev} ${operator_to_String()}`;
$steps.innerHTML = middleSteps;
}
//리셋버튼을 누른 경우
function on_reset(){
data.prev = '';
data.curr = '';
$display.innerHTML = '0';
$display.classList.add('off');
$steps.innerHTML = '0';
data.operator = undefined;
}
//이벤트 실행
$area_btn.addEventListener('click', (e) => {
const target = e.target;
if(target.tagName !== 'BUTTON') return;
if(target.id === 'reset') {
on_reset();
return;
}
if(target.className === 'num') {
on_num(data.operator, target);
}
if(target.className === 'op') {
on_ops(target);
}
if(target.id === 'btn_result') {
show_result();
}
});
JavaScript는 부분별로 기능에 대해 알아보자!
이벤트 실행
//HTML 불러오기
const $steps = document.querySelector('#steps');
const $display = document.querySelector('#display');
const $area_btn = document.querySelector('#area-btn');
//기본값
const data = {
prev : '',
curr : '',
operator: undefined,
pressedResult : false
}
//이벤트 실행
$area_btn.addEventListener('click', (e) => {
const target = e.target;
if(target.tagName !== 'BUTTON') return;
if(target.id === 'reset') {
on_reset();
return;
}
if(target.className === 'num') {
on_num(data.operator, target);
}
if(target.className === 'op') {
on_ops(target);
}
if(target.id === 'btn_result') {
show_result();
}
});
이벤트가 실행되는 코드이다.
먼저 이벤트의 target을 변수로 설정하고 그 target의 요소에 따라 실행되는 함수가 다르도록 설정한다.
<기본값>
기본값은 계산기가 디폴트로 가지게 될 값이다.
이전 값과 현재 값은 비어있는 값
연산자는 없는 값
result 버튼은 누르지 않은 상태를 기본으로 유지하며 입력값에 따라 값을 바꿔줄 것이다.
<이벤트>
1. 함수가 실행되면 안 되는 경우, 즉 target의 tagName이 BUTTON이 아닌 경우 실행되지 않도록 리턴을 한다.
2. 만약 target의 id가 reset이라면 ( C 버튼) on_reset 함수가 실행된다.
3. 만약 target의 className이 'num'이라면 (숫자 버튼이라면) on_num 함수가 실행되고 인자로 기본값의 operator와 target값을 준다.
4. 만약 target의 className이 'op'라면 on_ops함수가 실행되고 인자로 target 값을 준다.
5. 만약 target의 id가 'btn_result'라면 (= 버튼) show_result 함수를 실행한다.
숫자 버튼을 누른 경우
function on_num(operator, target) {
let prevOrcurr = operator ? 'curr' : 'prev';
const val = target.dataset.val;
if(val === "-1") {
if(data.curr !== '') {
data[prevOrcurr] = Number(data.curr)*-1 ;
} else{
data[[prevOrcurr]] = Number(data.prev)*-1 ;
}
} else{
data[prevOrcurr] += val;
}
$steps.innerHTML = data[prevOrcurr];
}
숫자 버튼을 눌렀을 때 실행되는 함수는 기본값의 operator와 target값을 받는다.
let prevOrcurr = operator ? 'curr' : 'prev';
연산자의 여부에 따라 이전 값에 넣을지 현재 값에 넣을지 지정하는 것이다.
만약 연산자 값이 true 라면 (undefined이 아니라면) 이전 값이 있는 것이므로 curr 값이 되어야한다. 그리고 연산자가 없다면 이전값이 없는 것이므로 prev 값이 되어야 한다.
const val = target.dataset.val;
if(val === "-1") {
if(data.curr !== '') {
data[prevOrcurr] = Number(data.curr)*-1 ;
} else{
data[[prevOrcurr]] = Number(data.prev)*-1 ;
}
} else{
data[prevOrcurr] += val;
}
html에서 설정한 data-val 값을 불러와서 입력값으로 지정할 것이다.
그래서 target.dataset.val을 변수로 지정해서 값을 가져온다. 이러한 값은 console.dir로 경로를 찾을 수 있다.
prevOrcurr 값은 'prev' 혹은 'curr'가 문자열 형태로 저장되어 있기 때문에 data [prevOrcurr]는 data 객체의 prev 값 혹은 curr 값을 가리킨다.
분별되어야 하는 값은 양수/음수로 변경하는 버튼이다. 이 버튼을 누르면 양수는 음수로, 음수는 양수로 변경되어야 한다.
그래서 만약 val 값이 -1이라면 (양수/음수 버튼을 누른 경우)
현재 값이 있다면 현재 값을 변경하고, 없다면 이전 값을 변경한다.
그리고 val 값이 아니라면 data prev 또는 curr 값에 val 값을 더한다.
$steps.innerHTML = data[prevOrcurr];
그리고 그 값을 출력한다.
연산자 버튼을 누른 경우
function on_ops(target){
$display.classList.remove('off');
on_result();
const dataOp = target.dataset.val;
data.operator = dataOp;
if(data.prev === undefined) return;
show_middleStep();
}
연산자 버튼을 누르면 연산자 함수는 target 값을 받는다.
$display.classList.remove('off');
on_result();
연산자 버튼을 누르면 display를 숨기는 class off를 삭제하여 display가 나타나도록 한다.
그리고 값을 계산하는 on_result 함수를 실행한다.
const dataOp = target.dataset.val;
data.operator = dataOp;
그리고 연산자 역시 val 값을 저장해서 data.operator에 저장한다.
if(data.prev === undefined) return;
show_middleStep();
만약 data의 이전 값이 없다면 이전값 없이 연산자가 나올 수 없기 때문에 return을 한다.
그리고 계산 과정을 보여줄 show_middleStep 함수를 실행한다.
연산 버튼을 누른 경우 계산하기
function on_result(){
if(data.prev === undefined || data.curr === undefined || data.operator === undefined) return;
data.prev = cal_sum();
$display.innerHTML = data.prev;
data.curr='';
}
만약 이전 값이 없거나, 현재 값이 없거나, 연산자가 없는 경우 계산할 수 없기 때문에 리턴한다.
그리고 현재 값을 계산하는 함수 cal_sum의 값을 이전 값에 저장하고 display에 출력한다.
만약 다음 숫자를 입력하는 경우 현재 값에 저장되도록 현재 값을 비운다.
계산하기
function cal_sum(){
const {prev,curr,operator} = data;
switch(operator){
case "+":
return Number(prev) + Number(curr);
case "-":
return Number(prev) - Number(curr);
case "*":
return Number(prev) * Number(curr);
case "/":
return Number(prev) / Number(curr);
}
}
data 객체의 prev, curr, operator 값을 변수로 사용할 수 있도록 지정한다.
그리고 switch 문을 통해 각 연산자마다 계산할 수 있도록 한다.
중간과정 출력하기
function show_middleStep(){
const middleSteps = `${data.prev} ${operator_to_String()}`;
$steps.innerHTML = middleSteps;
}
중간과정을 step에 출력할 수 있도록 한다. 하지만 연산자 곱하기, 나누기의 경우 컴퓨터에서 인식할 수 있도록 val 값이 *, /로 지정되어 있다. 하지만 출력될 때 × 나 ÷ 로 출력되면 더 깔끔하고 실제 계산기 같아서 좋을 것이다. 그래서 operator_to_String 함수를 통해 변환해준다. 그리고 이전 값과 그 변환된 연산자를 steps에 출력한다.
연산자 문자열로 변환하기
function operator_to_String(){
const {operator} = data;
switch (operator){
case "+" :
return "+";
case "-" :
return "-";
case "*" :
return "×";
case "/" :
return "÷";
}
}
switch 문을 통해 해당 연산자에 따라 문자열로 만들어준다.
= 버튼을 누른 경우
function show_result(){
if(data.prev === undefined || data.curr === undefined || data.operator === undefined) return;
$display.classList.add('off');
data.prev = cal_sum();
$steps.innerHTML = data.prev;
data.curr='';
}
= 버튼을 누른 경우는 on_result 함수에 거의 유사하다. 굳이 나눈 이유는 연산자를 눌렀을 땐 계산 과정이 아래 나오고 계산된 값이 위에 작게 나오지만 결과 버튼을 누르면 최종 계산 값이 아래에 나오게 하고 싶어서 함수를 나눴다.
결과 버튼을 누른 경우 display를 숨길 수 있었던 class off를 다시 추가한다. 그리고 on_result와 동일하게 계산한 값을 prev 값에 저장하고 그 값을 display가 아닌 steps에 출력한다.
그리고 현재 값을 초기화한다.
리셋 버튼을 누른 경우
function on_reset(){
data.prev = '';
data.curr = '';
$display.innerHTML = '0';
$display.classList.add('off');
$steps.innerHTML = '0';
data.operator = undefined;
}
리셋버튼을 누른 경우 data 객체 값을 모두 기본값으로 변경하면 된다. 그리고 출력 화면의 값도 처음 기본 상태로 되돌리면 완료!
오류 해결한 부분
위에서 언급한 것처럼 찾았던 오류는
1. 앞에서 입력한 연산자가 아닌 뒤의 연산자를 기준으로 계산된다.
2. 결괏값에서 양수/음수 버튼을 누르는 경우 값이 제대로 적용되지 않는다.
이것이었다.
먼저 1번 오류를 해결할 방법을 오래 고민했었다. 처음엔 두 번째 숫자를 누를 때 계산이 되도록 하고 싶었는데 실패했다. 그리고 계속 고민을 하다가 찾은 방법은 세상에서 제일 간단한 해결방법이었다...! 조건을 없애고 계산하는 on_result 함수를 먼저 실행되도록 한 것이었다.
2번 오류는 val이 -1일 때 조건을 추가했다. 계산하는 값이 모두 이전 값으로 저장되고 현재 값을 초기화하기 때문에 현재값이 없다면 이전값을 변경하고, 현재값이 있다면 현재값을 변경하도록 하였다. 그러면 결괏값에서 양수/음수 버튼을 누르는 경우 결괏값은 이전 값에 저장되어 있고 현재 값은 비어있기 때문에 이전 값의 양수/음수가 변경되고 결과적으로 결괏값이 변경된다.
개선하고 싶은 점
1. css 부분을 좀 더 발전시키고 싶다. 코드 이해하면서 공부용으로 만들어본 것이라 색이나 px 크기 등을 따로 root 변수로 지정하지 않고 바로 사용해서 아쉽다.
2. 이러한 코드의 경우 웹 접근성이 어떻게 되는지 알아보고 싶다. 아직 웹 접근성에 대해 익숙하지 않아서 더 공부해보고 적용하고 싶다.
3. 검색해보니 class를 이용해서 계산기를 만들기도 하던데 쉽게 이해가 안돼서 이 방법을 택했다. 그 방법으로도 한번 만들어보고 싶다.
4. 계산과정이 나올 때 이미 계산된 값이 되는 것이 아니라 3+4-5+... 이런식으로 입력했던 값이 모두 나오도록 하고 싶다!
이렇게 계산기 만들기가 완료되었다. 스스로 처음부터 만들진 못했지만 오류를 찾고 그 과정을 직접 해결할 수 있어서 좋았다.
만약 계산기에 오류가 있다면 언제든 알려주세요:)
'개발 공부 > JavaScript' 카테고리의 다른 글
JavaScript 이론 정리 #4 [구조 분해 할당, 나머지 매개 변수, 전개구문] (2) | 2022.08.27 |
---|---|
JavaScript 이론 정리 #3 [배열 메소드] (0) | 2022.08.17 |
JavaScript 이론 정리 #2 [number 메소드, Math 메소드, string 메소드] (0) | 2022.08.16 |
자바스크립트 이론 정리 #1 [변수 hosting, 생성자 함수, computed property, 객체 메소드, 심볼] (0) | 2022.08.08 |
자바스크립트로 이미지 슬라이드 쇼 만들기 (0) | 2022.07.19 |