프라이D
프라이Develog(❁´◡`❁)
프라이D
전체 방문자
오늘
어제
  • ALL (378)
    • TDD, Cleancode with JavaScr.. (5)
    • 프로젝트 (32)
      • work (3)
      • 직접 만드는 기술 블로그 (2)
      • 데일리 옥션 (19)
      • 모락모락 (8)
    • Computer Science (1)
    • Algorithm & 자료구조 (94)
      • 알고리즘 w.JavaScript (53)
      • 자료구조 (5)
      • (인프런) 자바스크립트 알고리즘 문제풀이 (34)
    • JavaScript (45)
      • JavaScript (41)
      • 모던 자바스크립트 Deep Dive (4)
    • WEB (13)
    • 회고 (12)
    • TIL (109)
    • WIL (7)
    • Stacks (20)
      • React.js (6)
      • Next.js (1)
      • Redux (3)
      • Node.js (2)
      • GIT (2)
      • SAP (1)
    • 15일 메이킹 프로젝트 (15)
    • 이전 기록 (14)
    • ETC. (5)
    • ---------------2021 (6)
      • 내일배움단-웹개발 5주 (2)
      • 정보처리기사 (4)

블로그 메뉴

  • 홈
  • 태그
  • 미디어로그
  • 위치로그
  • 방명록

공지사항

인기 글

태그

  • nomadcoders
  • 비트마스크
  • 코드스테이츠
  • Til
  • 자바스크립트
  • nomadcoder
  • 코딩프로젝트
  • JavaScript
  • 내일배움단
  • MySQL
  • 자바스크립트알고리즘
  • 투포인터알고리즘
  • 모던자바스크립트딥다이브
  • 자바스크립트비트마스크
  • 알고리즘
  • 스파르타코딩클럽
  • vanilaJS
  • 국비지원
  • 2023 인프콘 후기
  • 내일배움카드

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
프라이D

프라이Develog(❁´◡`❁)

[JavaScript] HTML, CSS, JS로 만든 웹계산기
JavaScript/JavaScript

[JavaScript] HTML, CSS, JS로 만든 웹계산기

2022. 6. 9. 17:52

자바스크립트로 만든 웹계산기

 

I am a Calculator

 

hyejj19.github.io

무엇을 만들어볼까 하다가, 자주 쓰는 계산기를 만들어 보았다.

다른 글들을 참고해보니 eval() 함수를 사용해 문자열 자체를 코드로 변환하여 연산하는 방식도 있었는데, 외부에서 입력된 코드를 그대로 실행시킨다는 점이 자칫 치명적인 단점이 될 수 있다는 우려가 있다. 따라서 eval()을 사용하지 않고 입력값을 변환하여 내부적으로 계산하는 방식으로 만들게 되었다. 참고로 ? << 물음표 버튼은 아직 기능이 없다.

 

기능 구현 목표는

1. 사칙연산

2. AC와 delete 기능

See the Pen Calculator by hyejj19 (@hyejj19) on CodePen.

CSS

main {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  grid-template-rows: 0.8fr 0.9fr repeat(5, 1fr);
  grid-gap: 1.2rem;

  background-color: #ececee;
  border-radius: 0.7rem;
  height: 43rem;
  width: 25rem;
  padding: 2rem;

  box-shadow: rgba(0, 0, 0, 0.07) 0px 1px 2px, rgba(0, 0, 0, 0.07) 0px 2px 4px,
    rgba(0, 0, 0, 0.07) 0px 4px 8px, rgba(0, 0, 0, 0.07) 0px 8px 16px,
    rgba(0, 0, 0, 0.07) 0px 16px 32px, rgba(0, 0, 0, 0.07) 0px 32px 64px;
}

.logo {
  grid-column: 1 / span 4;
  grid-row: 1 / 2;

  display: flex;
  align-items: flex-end;

  font-size: 1.3rem;
  margin-bottom: 0.8rem;
}

.input--wrapper {
  grid-column: 1 / span 4;
  grid-row: 2 / 3;

  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.5rem;
  margin-bottom: 1.5rem;
  background-color: #b8b7bd;

  box-shadow: rgba(0, 0, 0, 0.15) 1px 1px 1px 1px inset;
}

.input {
  display: flex;
  align-items: center;
  justify-content: flex-end;

  background-color: #878875;
  width: 100%;
  height: 100%;
  padding: 0.2rem 0.8rem;
  font-size: 2.6rem;
  font-stretch: condensed;

  box-shadow: rgba(0, 0, 0, 0.08) 2px 3px 1px 1px inset;
}

.keypads {
  display: flex;
  justify-content: center;
  align-items: center;

  background-color: white;
  font-size: 1.3rem;
  border-radius: 50%;
  color: #0a0908;

  box-shadow: rgba(0, 0, 0, 0.02) 1px 5px 3px 1px,
    rgba(27, 31, 35, 0.15) 0px 2px 1px 1px;

  transition: all 0.1s;
  cursor: pointer;
}

.keypads:hover {
  background-color: #e6e6e6;
}

/* 키 예외 */
.key--clear {
  background-color: #d1281b;
  color: black;
}
.key--clear:hover {
  background-color: #f91504;
}

.key--equals {
  background-color: #f2b90c;
  color: black;
}
.key--equals:hover {
  background-color: #ffd500;
}

.key--function {
  color: #477934;
}
.key--function:hover {
  background-color: #477934;
  color: white;
}

/* 예외 끝 */

CSS는 Grid를 사용하여 레이아웃을 구성했다. flex만 써 보고 grid는 처음 사용해 보았는데, 요소를 원하는 위치에 배열하기가 간편하고, 간격과 크기도 원하는대로 조절할 수 있다는 점이 편리했다.

또 요소의 단위를 px 말고 rem으로 사용해 보았는데, 기준을 두고 상대적인 크기로 값을 정할 수 있어서 편리했다. 

JS

  • 먼저 코드 작성에 필요한 DOM 요소를 선택해주었다.
// ELEMENTS
const labelInput = document.querySelector('.input');
const keyClear = document.querySelector('.key--clear');
const keyDelete = document.querySelector('.key--delete');
const keyFunctions = document.querySelectorAll('.key--function');
const keyEquals = document.querySelector('.key--equals');
const keypads = document.querySelectorAll('.keypads');
const keyDot = document.querySelector('.key--dot');
  • value값과 input 값이 표시될 labelInput과 clear키, delete키, 연산결과키, 소수점키를 각각 따로 가져왔고, 사칙연산키와 숫자키는 각각 keyFunctions, keypads 로 모두 가져와 주었다.
// 계산에 필요한 데이터
  let value = 0;
  let inputValue = '';
  let operatorPre = '';
  let operator = '';
  • 연산을 수행하는데 필요한 데이터는 위와 같다. 이전 연산 값을 가진 value, 현재 입력된 값인 inputValue, 이전에 눌렀던 연산자 값인 operatorPre, 그리고 현재 입력한 연산자 operator.

이벤트 리스너 등록 

  • 다음으로 각 키에 이벤트 리스너를 달아, 기능에 맞는 값을 넣어 함수를 호출하도록 코드를 짰다.
// 이벤트 리스너
  // 숫자 클릭이벤트 감지
  keypads.forEach((key) => {
    key.addEventListener('click', (e) => {
      let num = e.target.textContent.trim();
      setNumber(num);
    });
  });

  // 연산자 클릭이벤트 감지
  keyFunctions.forEach((key) =>
    key.addEventListener('click', (e) => {
      let key = e.target.textContent.trim();
      setOperator(key);
    })
  );

  // equals, clear, deleteOne, dot 클릭감지
  keyEquals.addEventListener('click', () => {
    equals();
  });
  keyClear.addEventListener('click', () => {
    clear();
  });
  keyDelete.addEventListener('click', () => {
    deleteOne();
  });
  keyDot.addEventListener('click', () => {
    addDot();
  });

  // 키보드 이벤트 감지
  window.addEventListener('keydown', function (e) {
    let key = e.key;
    if (!isNaN(key)) setNumber(key);
    else if (key === '/' || key === '*' || key === '-' || key === '+')
      setOperator(key);
    else if (key === 'Enter') equals();
    else if (key === 'Backspace') deleteOne();
    else if (key === 'Escape') clear();
    else if (key === '.') addDot();
  });
  • 키보드 이벤트도 해당 키보드의 key 값을 체크해서 조건에 맞는 함수가 실행되도록 하였다. 

함수 작성

 // 숫자 입력값 생성
 const setNumber = function (num) {
    if (!isNaN(num) && inputValue.length < 12) {
      inputValue += num;
      labelInput.textContent = inputValue;
    }
  };
  • setNumber 함수는 keypads를 눌렀을 때 동작하는 함수인데, keypads 클래스가 모든 버튼에 적용되어 있으므로 해당 값이 숫자값인지 체크하고, 입력창에 한계가 있으므로 길이를 제한하여 화면에 출력하였다.
// 연산자 입력값 생성
const setOperator = function (key) {
    operatorPre = operator;
    operator = key;
    if (value && inputValue) {
      value = labelInput.textContent = operate(operatorPre);
    }
    value = value || inputValue;
    inputValue = '';
  };
  • setOperator 함수는 사칙연산 키를 눌렀을 때 동작하는 함수이다.
    먼저 이전 연산자 값을 operatorPre 에 저장하고, 새롭게 입력된 key 값을 operator에 재할당한다. 이 값을 가지고 이전에 이미 입력된 value값과 현재 inputValue가 존재할 때 이전 연산자를 가지고 연산하여 해당 결과값을 출력한 뒤 연산을 이어갈 수 있다.
    다음 계산을 할 value는 value가 0일 때는 현재 입력된 값으로 할당하고, 이미 존재하면 존재하는 값이 된다. 
// 계산 실행 함수
  const operate = function (operator) {
    value = Number(value);
    inputValue = Number(inputValue);

    switch (operator) {
      case '/':
        value /= inputValue;
        break;
      case '*':
        value *= inputValue;
        break;
      case '-':
        value -= inputValue;
        break;
      case '+':
        value += inputValue;
        break;
    }
    // 출력값의 자릿수 제한
    if (value.toString().length > 12) {
      value = value.toString().slice(0, 13);
    }
    return value.toLocaleString();
  };
  • 연산자를 받아 연산을 실행하는 함수이다. 함수 실행시 받아온 연산자로 switch문을 돌려 결과값을 반환한다.
    연산 결과의 출력값이 입력 창의 범위를 넘어가면, 넘어가지 않는 범위만 출력하도록 한다.
    3자리마다 자리수 구분을 하기 위해 str.toLocaleString(); 함수를 사용했다.
  // equals, clear, deleteOne, addDot
  const equals = function () {
    value = labelInput.textContent = operate(operator);
    operator = '';
  };
  const clear = function () {
    value = 0;
    inputValue = '';
    operator = '';
    labelInput.textContent = value;
  };
  const deleteOne = function () {
    inputValue = inputValue.slice(0, -1);
    labelInput.textContent = inputValue || 0;
  };
  const addDot = function () {
    inputValue += '.';
    labelInput.textContent = inputValue;
  };
  • 나머지 기능을 하는 함수는 위와 같다. 

느낀 점

간단하게 만들 수 있다고 생각해서 코드 구조나 사용 흐름을 잘 고려하지 않고 만들기 시작했다.

만들면서 하나씩 문제점이 생겼는데, 먼저 연산키와 입력 값을 연속적으로 누를 때 새로 바뀐 연산자를 고려하지 않고 이전의 연산을 그대로 반복하는 오류였다. 고민을 하다 이 부분은 이전의 연산자 값을 저장하고, 특정 조건에서 이전의 값을 반영하여 연산하도록 작성해서 해결할 수 있었다.

그리고 키보드 이벤트를 감지할 때 해당 기능을 수행하는 함수를 클릭 이벤트리스너의 콜백 함수로 모조리 작성해버려서 다시 별도의 함수로 분리한 뒤 호출하는 방식으로 변경했다.

 

이렇게 중간중간 바뀌는 부분이 많아서 조금 더 깔끔하게 작성하지 못한 것 같은데, 다음에는 코드를 짜기 전에 어떤 구조로 만들지, 어떤 기능을 만들지 보다 명확하게 결정하고 만들어야겠다.

저작자표시 (새창열림)

'JavaScript > JavaScript' 카테고리의 다른 글

[JavaScript] 문자열 메서드 정리  (0) 2022.06.27
[JavaScript] 데이터 타입  (0) 2022.06.24
[JavaScript] arr.sort() 메서드 (배열의 정렬)  (0) 2022.05.19
[JavaScript] 원시타입 vs 참조타입  (0) 2022.04.24
[JavaScript] this keyword  (0) 2022.04.18
    'JavaScript/JavaScript' 카테고리의 다른 글
    • [JavaScript] 문자열 메서드 정리
    • [JavaScript] 데이터 타입
    • [JavaScript] arr.sort() 메서드 (배열의 정렬)
    • [JavaScript] 원시타입 vs 참조타입
    프라이D
    프라이D
    틀린내용 정정 및 개선사항은 언제든지 댓글 달아주세요 :D

    티스토리툴바