원래 내 첫번째 개발 도전 프로젝트였으나 중간에 한번 큰 벽에 막혀서 잠시 멈추고 다른 프로젝트 (한국 영화 박스오피스 순위)를 먼저 진행해보고 다시 재도전해서 완성한 프로젝트.
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 );
어쩌다보니 내 능력대비 너무 하기 힘든 프로젝트로 설정해서 개고생하긴 했었지만 정말 많은부분 배울 수 있었던 것 같다.