나의 개발 일지

환율 계산기 만들기 프로젝트

designer DK 2024. 7. 28. 14:36
728x90

원래 내 첫번째 개발 도전 프로젝트였으나 중간에 한번 큰 벽에 막혀서 잠시 멈추고 다른 프로젝트 (한국 영화 박스오피스 순위)를 먼저 진행해보고 다시 재도전해서 완성한 프로젝트.

 

https://currencyex.netlify.app

 

 

개발 독학 공부를 시작하면서 참고했던 유튜브 채널인 '코딩알려주는누나' 채널에서 알려준 환율계산기를 한번 만들어보고서 조금만 더 보강하면 제대로된 환율계산기를 만들 수 있겠구나~라고 생각했었지만 그건 큰 착각이었었다.

막상 실제 api를 가져와서 진행해보니 여러가지로 예상치못했던 변수나 힘든 부분들도 많았다.

 

 

무료로 환율정보를 가져올 수 있는 api 를 열심히 찾다가 이 곳이 가장 적절해보여서 여기서 당겨왔었다.

https://currencyapi.com/

 

Currency Conversion API | Currencyapi.com

Currencyapi.com is a free, reliable & feature complete currency exchange rates API. 170+ world currencies & 60 second update rate.

currencyapi.com

 

 

API 사용관련 정보

 

나는 이 사이트에서 최신환율 api를 가져왔는데 기본적으로 meta와 data로 나눠져있었다.

(meta: 최신 업데이트 날짜 정보, data: 국가별 환율정보)

 

추가적으로 base_currency와 currencies라는 속성을 제공해주었었는데 여기에 대한 설명이 구체적이지 않아서
매우 고생했었다. (각각 기준환율 대 거기 상응하는 환율 같은 개념이었음)

 

전역변수로 fromCurrency와 toCurrency라는 변수를 만들어서 각기 base_currency와 currencies에 대응 시켰다.

(첫 시도 때는 전역/지역변수 개념도 잘 인지되지 않았던 상태라 더 해맸었다 ㅜㅜ)

let fromCurrency = "USD";
let toCurrency = "USD";
method: "GET",
            data: { base_currency: fromCurrency, currencies : toCurrency }
 

 

 

fromCurrency는 위쪽 인풋에서 설정
toCurrency는 아래쪽 인풋에 나타나도록 설정

 

 

먼저 드랍다운메뉴를 구성해보았다.

자주 사용되는 환율 국가들을 nationTop이라고해서 15개 배열 안 객체로 묶었고

let nationTop=[
    {id: 0, unit: "USD", flag: "data/flags/USD.png"},
    {id: 1, unit: "EUR", flag: "data/flags/EUR.png"},
    {id: 2, unit: "CNY", flag: "data/flags/CNY.png"},
    {id: 3, unit: "JPY", flag: "data/flags/JPY.png"},
    {id: 4, unit: "GBP", flag: "data/flags/GBP.png"},
    {id: 5, unit: "HKD", flag: "data/flags/HKD.png"},
    {id: 6, unit: "AUD", flag: "data/flags/AUD.png"},
    {id: 7, unit: "INR", flag: "data/flags/INR.png"},
    {id: 8, unit: "SGD", flag: "data/flags/SGD.png"},
    {id: 9, unit: "KRW", flag: "data/flags/KRW.png"},
    {id: 10, unit: "CHF", flag: "data/flags/CHF.png"},
    {id: 11, unit: "RUB", flag: "data/flags/RUB.png"},
    {id: 12, unit: "BRL", flag: "data/flags/BRL.png"},
    {id: 13, unit: "CAD", flag: "data/flags/CAD.png"},
    {id: 14, unit: "MXN", flag: "data/flags/MXN.png"},
    {id: 15, unit: "VND", flag: "data/flags/VND.png"}
]

 

그 외 국가들을 알파벳 티커순으로해서 nation이라는 배열 안 객체로 묶었다.
각 객체 안에 id, unit, flag 정보가 있다.

 

그리고 그 객체 정보들을 forEach문을 돌리고 insertAdjacentHTML을 통해서 html안에 삽입하는 방식으로 메뉴를 만들었다.

// 환율 메뉴 구성 from
nationTop.forEach((data) => {
    const dropdownMenu = `
      <div class="dropdown-menu from-currency-list">
          <img src="${data.flag}" alt="">
          <a href="#">${data.unit}</a>
      </div>
      `;
    document
      .querySelector("#from-container-list1")
      .insertAdjacentHTML("beforeend", dropdownMenu);
  });
nation.forEach((data) => {
  const dropdownMenu = `
    <div class="dropdown-menu from-currency-list">
        <img src="${data.flag}" alt="">
        <a href="#">${data.unit}</a>
    </div>
    `;
  document
    .querySelector("#from-container-list2")
    .insertAdjacentHTML("beforeend", dropdownMenu);
});
// 환율 메뉴 구성 to
nationTop.forEach((data) => {
    const dropdownMenu = `
      <div class="dropdown-menu to-currency-list">
          <img src="${data.flag}" alt="">
          <a href="#">${data.unit}</a>
      </div>
      `;
    document
      .querySelector("#to-container-list1")
      .insertAdjacentHTML("beforeend", dropdownMenu);
  });
nation.forEach((data) => {
  const dropdownMenu = `
    <div class="dropdown-menu to-currency-list">
        <img src="${data.flag}" alt="">
        <a href="#">${data.unit}</a>
    </div>
    `;
  document
    .querySelector("#to-container-list2")
    .insertAdjacentHTML("beforeend", dropdownMenu);
});

 

 

 

 

 

그리고 각 드랍다운메뉴에서 선택한 값이 인풋창에 적용도 되면서 
각각 base_currency와 currencies 값에 재적용되는 기능을 구현했다.

// 환율 메뉴 클릭 시 메뉴 정보 변경 기능 from
document.querySelectorAll(".from-currency-list").forEach((menu)=>
    menu.addEventListener("click", function(){
        document.getElementById("from-button-text").textContent = this.textContent;
        document.getElementById("from-button-img").src = this.childNodes[1].src;
        fromCurrency = this.textContent;
       

        $.ajax({
            method: "GET",
            data: { base_currency: fromCurrency, currencies : toCurrency }
     
        }).done(function (msg) {
            // 객체를 배열로 변환
            currencyRatioArray = Object.values(msg.data);
            currencyRatio = currencyRatioArray[0].value;
            convert();
        });
    })
);
// 환율 메뉴 클릭 시 메뉴 정보 변경 기능 to
document.querySelectorAll(".to-currency-list").forEach((menu)=>
    menu.addEventListener("click", function(){
        document.getElementById("to-button-text").textContent = this.textContent;
        document.getElementById("to-button-img").src = this.childNodes[1].src;
        toCurrency = this.textContent;
       
        $.ajax({
            method: "GET",
            data: { base_currency: fromCurrency, currencies : toCurrency }

        }).done(function (msg) {
        //   객체를 배열로 변환
          currencyRatioArray = Object.values(msg.data);
          currencyRatio = currencyRatioArray[0].value;
          convert();
        });
})
);

 

이부분에서 매우 방황했었고 잠깐 다른 프로젝트를 먼저하고 다시와서야 해결했었다.
api를 통해 받아온 데이터가 base_currency나 currencies로 들어올 때 계속 객체타입으로 들어와서 실질적으로 사용해야할 값으로 넣어주지 못해서 매우 고생했었음 ㅜㅜ
많은 시행착오 끝에 객체를 배열로 변환을 하는 솔루션을 발견했고 거기서 값을 빼 올 수 있었다,

// 객체를 배열로 변환
            currencyRatioArray = Object.values(msg.data);
            currencyRatio = currencyRatioArray[0].value;

 

 

드랍다운을 토글식으로 제어하는 구문은 최대한 무난해보이는 샘플을 가져와서 고쳐서 썼다.

// 드랍다운 메뉴 팝업 관련 기능
const toggleDropdown1 = function(){
    document.querySelector("#from-container").classList.toggle("show");
    document.querySelector("#arrow1").classList.toggle("arrow");
    }
    document.querySelector("#from-dropdown").addEventListener("click", function(e){
        e.stopPropagation();
        toggleDropdown1();
        document.querySelector("#to-container").classList.remove("show");
        document.querySelector("#arrow2").classList.remove("arrow");
    });
const toggleDropdown2 = function(){
    document.querySelector("#to-container").classList.toggle("show");
    document.querySelector("#arrow2").classList.toggle("arrow");
    }
    document.querySelector("#to-dropdown").addEventListener("click", function(e){
        e.stopPropagation();
        toggleDropdown2();
        document.querySelector("#from-container").classList.remove("show");
        document.querySelector("#arrow1").classList.remove("arrow");

    });
document.documentElement.addEventListener("click", function(){
    if(document.querySelector("#from-container").classList.contains("show")){
        toggleDropdown1();
    }
    if(document.querySelector("#to-container").classList.contains("show")){
        toggleDropdown2();
    }
});

 

 

 

특히 메뉴창 비저블 유무나 화살표에 대한 모션을 자바스크립트 - css 연동하는 방법으로 제어했었는데 이게 굉장히 신기했었다,

/* 기능 관련 */
.show {
  visibility: visible;
  opacity: 1;
}

.arrow {
  transform: rotate(180deg);
  transition: 0.2s ease;
}

 

 

그 외에도 디테일한 제어관련 부분들을 많이 적용해가면서 공부할 수 있는 부분이 많았다.

 

소수점 자리수 제어, 숫자 3자리마다 콤마

// 소수점 자리수
    document.getElementById("to-input").value = convertedAmount.toFixed(2);
    document.getElementById("result-quote").textContent = convertedAmount.toFixed(2);
// 숫자 콤마
    document.getElementById("to-input").value = convertedAmount.toLocaleString();
    document.getElementById("result-base").textContent = amount;
    document.getElementById("result-quote").textContent = convertedAmount.toLocaleString();
    document.getElementById("currency-base").textContent = fromCurrency;
    document.getElementById("currency-quote").textContent = toCurrency;

 

 

입력 자리수 늘어날 때 폰트사이즈 연동

let result1 = document.getElementById("result-base").textContent
    let result2 = document.getElementById("result-quote").textContent

    if(amount.length > 9 || result2.length >12){
        document.getElementById("result-base").classList.add("text-size-small")
        document.getElementById("result-quote").classList.add("text-size-small")
        document.getElementById("currency-base").classList.add("text-size-small")
        document.getElementById("currency-quote").classList.add("text-size-small")
        document.getElementById("equal").classList.add("text-size-small")
    } else if (amount.length <= 9 || result.length <= 12){
        document.getElementById("result-base").classList.remove("text-size-small")
        document.getElementById("result-quote").classList.remove("text-size-small")
        document.getElementById("currency-base").classList.remove("text-size-small")
        document.getElementById("currency-quote").classList.remove("text-size-small")
        document.getElementById("equal").classList.remove("text-size-small")
    }

 

 

인풋입력시 텍스트타입이면서도 숫자만 입력되도록 하기 (마이너스, 소수점, 콤마 제어)

(넘버타입으로하면 3자리마다 콤마 넣는게 안되어서...)

//인풋을 텍스트타입으로 하면서 숫자만 입력 + (마이너스, 소수점, 콤마 제어)
function numberFormat(val, isMinus, isFloat, isComma){
    var str = val;
    //일단 마이너스, 소수점을 제외한 문자열 모두 제거
    str = str.replace(/[^-\.0-9]/g, '');
    //마이너스
    if(isMinus){
       str = chgMinusFormat(str);  
    } else {
       str = str.replace('-','');
    }
   
    //소수점
    if(isFloat){
       str = chgFloatFormat(str);      
    } else {
       if(!isMinus ){
          str = str.replace('-','');
       }
       str = str.replace('.','');
       if(str.length>1){
          str = Math.floor(str);
          str = String(str);
       }
    }
   
    //콤마처리
    if(isComma){
       var parts = str.toString().split('.');
       if(str.substring(str.length - 1, str.length)=='.'){
          str = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g,",") +".";
       } else {
          str = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g,",") + (parts[1] ? "." + parts[1] : "");
       }
    }
 
    return str;
  }
 
  function chgFloatFormat(str){
    var idx = str.indexOf('.');  
    if(idx<0){  
       return str;
    } else if(idx>0){
       var tmpStr = str.substr(idx+1);    
       if(tmpStr.length>1){            
          if(tmpStr.indexOf('.')>=0){        
             tmpStr =  tmpStr.replace(/[^\d]+/g, '');                  
             str = str.substr(0,idx+1) + tmpStr;
          }
       }    
    } else if(idx==0){
          str = '0'+str;
    }
    return str;  
  }
   
  function chgMinusFormat(str){
    var idx = str.indexOf('-');
    if(idx==0){
    var tmpStr = str.substr(idx+1);
       //뒤에 마이너스가 또 있는지 확인
       if(tmpStr.indexOf('-')>=0){
             tmpStr = tmpStr.replace('-','');
          str = str.substr(0,idx+1) + tmpStr;  
       }
    } else if(idx>0){
          str = str.replace('-','');
    } else if(idx<0){
          return str;
    }
       return str;
  }

 

 

스크롤에대한 설정도 처음해봤는데 신기했다.

body {
  margin: 0px;
  display: flex;
  flex-direction: column;
  height: 100vh;

  ::-webkit-scrollbar {
    width: 12px;  /* 세로축 스크롤바 폭 너비 */
  }
  ::-webkit-scrollbar-thumb {
    background: #BBC4D1; /* 스크롤바 막대 색상 */
    border-radius: 12px 12px 12px 12px;
  }
}
.dropdown-menucontainer {
  display: flex;
  flex-direction: column;
  position: absolute;
  top: 58px;
  left: -210px;
  background-color: #E5EBF5;
  z-index: 3;
  padding: 16px 16px 40px 16px;
  gap: 24px;
  scrollbar-gutter: stable;
  max-height: calc(100vh -  600px);
  overflow-y: auto;
  border-radius: 8px;
  box-shadow: 2px 2px 20px rgba(0, 0, 0, 0.4);

 

 

 

어쩌다보니 내 능력대비 너무 하기 힘든 프로젝트로 설정해서 개고생하긴 했었지만 정말 많은부분 배울 수 있었던 것 같다.