Currency Converter (HTML, CSS and JavaScript) - Data by Exchange rates API (https://exchangeratesapi.io)
A Pen by Coding Journey on CodePen.
Currency Converter (HTML, CSS and JavaScript) - Data by Exchange rates API (https://exchangeratesapi.io)
A Pen by Coding Journey on CodePen.
<div class="container"> | |
<div class="header"> | |
<h1>Currency Converter</h1> | |
</div> | |
<div class="date"></div> | |
<ul class="currencies"></ul> | |
<button class="add-currency-btn"><i class="fas fa-long-arrow-alt-left"></i>Add Currency</button> | |
<ul class="add-currency-list"></ul> | |
</div> |
// Global Variables | |
const addCurrencyBtn = document.querySelector(".add-currency-btn"); | |
const addCurrencyList = document.querySelector(".add-currency-list"); | |
const currenciesList = document.querySelector(".currencies"); | |
const dataURL = "https://api.exchangeratesapi.io/latest"; | |
const initiallyDisplayedCurrencies = ["USD", "EUR", "GBP", "JPY", "RUB"]; | |
let baseCurrency; | |
let baseCurrencyAmount; | |
let currencies = [ | |
{ | |
name: "US Dollar", | |
abbreviation: "USD", | |
symbol: "\u0024", | |
flagURL: "http://www.geonames.org/flags/l/us.gif" | |
}, | |
{ | |
name: "Euro", | |
abbreviation: "EUR", | |
symbol: "\u20AC", | |
flagURL: "https://upload.wikimedia.org/wikipedia/commons/b/b7/Flag_of_Europe.svg" | |
}, | |
{ | |
name: "Japanese Yen", | |
abbreviation: "JPY", | |
symbol: "\u00A5", | |
flagURL: "http://www.geonames.org/flags/l/jp.gif" | |
}, | |
{ | |
name: "British Pound", | |
abbreviation: "GBP", | |
symbol: "\u00A3", | |
flagURL: "http://www.geonames.org/flags/l/uk.gif" | |
}, | |
{ | |
name: "Australian Dollar", | |
abbreviation: "AUD", | |
symbol: "\u0024", | |
flagURL: "http://www.geonames.org/flags/l/au.gif" | |
}, | |
{ | |
name: "Canadian Dollar", | |
abbreviation: "CAD", | |
symbol: "\u0024", | |
flagURL: "http://www.geonames.org/flags/l/ca.gif" | |
}, | |
{ | |
name: "Swiss Franc", | |
abbreviation: "CHF", | |
symbol: "\u0043\u0048\u0046", | |
flagURL: "http://www.geonames.org/flags/l/ch.gif" | |
}, | |
{ | |
name: "Chinese Yuan Renminbi", | |
abbreviation: "CNY", | |
symbol: "\u00A5", | |
flagURL: "http://www.geonames.org/flags/l/cn.gif" | |
}, | |
{ | |
name: "Swedish Krona", | |
abbreviation: "SEK", | |
symbol: "\u006B\u0072", | |
flagURL: "http://www.geonames.org/flags/l/se.gif" | |
}, | |
{ | |
name: "New Zealand Dollar", | |
abbreviation: "NZD", | |
symbol: "\u0024", | |
flagURL: "http://www.geonames.org/flags/l/nz.gif" | |
}, | |
{ | |
name: "Mexican Peso", | |
abbreviation: "MXN", | |
symbol: "\u0024", | |
flagURL: "http://www.geonames.org/flags/l/mx.gif" | |
}, | |
{ | |
name: "Singapore Dollar", | |
abbreviation: "SGD", | |
symbol: "\u0024", | |
flagURL: "http://www.geonames.org/flags/l/sg.gif" | |
}, | |
{ | |
name: "Hong Kong Dollar", | |
abbreviation: "HKD", | |
symbol: "\u0024", | |
flagURL: "http://www.geonames.org/flags/l/hk.gif" | |
}, | |
{ | |
name: "Norwegian Krone", | |
abbreviation: "NOK", | |
symbol: "\u006B\u0072", | |
flagURL: "http://www.geonames.org/flags/l/no.gif" | |
}, | |
{ | |
name: "South Korean Won", | |
abbreviation: "KRW", | |
symbol: "\u20A9", | |
flagURL: "http://www.geonames.org/flags/l/kr.gif" | |
}, | |
{ | |
name: "Turkish Lira", | |
abbreviation: "TRY", | |
symbol: "\u20BA", | |
flagURL: "http://www.geonames.org/flags/l/tr.gif" | |
}, | |
{ | |
name: "Russian Ruble", | |
abbreviation: "RUB", | |
symbol: "\u20BD", | |
flagURL: "http://www.geonames.org/flags/l/ru.gif" | |
}, | |
{ | |
name: "Indian Rupee", | |
abbreviation: "INR", | |
symbol: "\u20B9", | |
flagURL: "http://www.geonames.org/flags/l/in.gif" | |
}, | |
{ | |
name: "Brazilian Real", | |
abbreviation: "BRL", | |
symbol: "\u0052\u0024", | |
flagURL: "http://www.geonames.org/flags/l/br.gif" | |
}, | |
{ | |
name: "South African Rand", | |
abbreviation: "ZAR", | |
symbol: "\u0052", | |
flagURL: "http://www.geonames.org/flags/l/za.gif" | |
}, | |
{ | |
name: "Philippine Peso", | |
abbreviation: "PHP", | |
symbol: "\u20B1", | |
flagURL: "http://www.geonames.org/flags/l/ph.gif" | |
}, | |
{ | |
name: "Czech Koruna", | |
abbreviation: "CZK", | |
symbol: "\u004B\u010D", | |
flagURL: "http://www.geonames.org/flags/l/cz.gif" | |
}, | |
{ | |
name: "Indonesian Rupiah", | |
abbreviation: "IDR", | |
symbol: "\u0052\u0070", | |
flagURL: "http://www.geonames.org/flags/l/id.gif" | |
}, | |
{ | |
name: "Malaysian Ringgit", | |
abbreviation: "MYR", | |
symbol: "\u0052\u004D", | |
flagURL: "http://www.geonames.org/flags/l/my.gif" | |
}, | |
{ | |
name: "Hungarian Forint", | |
abbreviation: "HUF", | |
symbol: "\u0046\u0074", | |
flagURL: "http://www.geonames.org/flags/l/hu.gif" | |
}, | |
{ | |
name: "Icelandic Krona", | |
abbreviation: "ISK", | |
symbol: "\u006B\u0072", | |
flagURL: "http://www.geonames.org/flags/l/is.gif" | |
}, | |
{ | |
name: "Croatian Kuna", | |
abbreviation: "HRK", | |
symbol: "\u006B\u006E", | |
flagURL: "http://www.geonames.org/flags/l/hr.gif" | |
}, | |
{ | |
name: "Bulgarian Lev", | |
abbreviation: "BGN", | |
symbol: "\u043B\u0432", | |
flagURL: "http://www.geonames.org/flags/l/bg.gif" | |
}, | |
{ | |
name: "Romanian Leu", | |
abbreviation: "RON", | |
symbol: "\u006C\u0065\u0069", | |
flagURL: "http://www.geonames.org/flags/l/ro.gif" | |
}, | |
{ | |
name: "Danish Krone", | |
abbreviation: "DKK", | |
symbol: "\u006B\u0072", | |
flagURL: "http://www.geonames.org/flags/l/dk.gif" | |
}, | |
{ | |
name: "Thai Baht", | |
abbreviation: "THB", | |
symbol: "\u0E3F", | |
flagURL: "http://www.geonames.org/flags/l/th.gif" | |
}, | |
{ | |
name: "Polish Zloty", | |
abbreviation: "PLN", | |
symbol: "\u007A\u0142", | |
flagURL: "http://www.geonames.org/flags/l/pl.gif" | |
}, | |
{ | |
name: "Israeli Shekel", | |
abbreviation: "ILS", | |
symbol: "\u20AA", | |
flagURL: "http://www.geonames.org/flags/l/il.gif" | |
} | |
]; | |
// Event Listeners | |
addCurrencyBtn.addEventListener("click", addCurrencyBtnClick); | |
function addCurrencyBtnClick(event) { | |
addCurrencyBtn.classList.toggle("open"); | |
} | |
addCurrencyList.addEventListener("click", addCurrencyListClick); | |
function addCurrencyListClick(event) { | |
const clickedListItem = event.target.closest("li"); | |
if(!clickedListItem.classList.contains("disabled")) { | |
const newCurrency = currencies.find(c => c.abbreviation===clickedListItem.getAttribute("data-currency")); | |
if(newCurrency) newCurrenciesListItem(newCurrency); | |
} | |
} | |
currenciesList.addEventListener("click", currenciesListClick); | |
function currenciesListClick(event) { | |
if(event.target.classList.contains("close")) { | |
const parentNode = event.target.parentNode; | |
parentNode.remove(); | |
addCurrencyList.querySelector(`[data-currency=${parentNode.id}]`).classList.remove("disabled"); | |
if(parentNode.classList.contains("base-currency")) { | |
const newBaseCurrencyLI = currenciesList.querySelector(".currency"); | |
if(newBaseCurrencyLI) { | |
setNewBaseCurrency(newBaseCurrencyLI); | |
baseCurrencyAmount = Number(newBaseCurrencyLI.querySelector(".input input").value); | |
} | |
} | |
} | |
} | |
function setNewBaseCurrency(newBaseCurrencyLI) { | |
newBaseCurrencyLI.classList.add("base-currency"); | |
baseCurrency = newBaseCurrencyLI.id; | |
const baseCurrencyRate = currencies.find(currency => currency.abbreviation===baseCurrency).rate; | |
currenciesList.querySelectorAll(".currency").forEach(currencyLI => { | |
const currencyRate = currencies.find(currency => currency.abbreviation===currencyLI.id).rate; | |
const exchangeRate = currencyLI.id===baseCurrency ? 1 : (currencyRate/baseCurrencyRate).toFixed(4); | |
currencyLI.querySelector(".base-currency-rate").textContent = `1 ${baseCurrency} = ${exchangeRate} ${currencyLI.id}`; | |
}); | |
} | |
currenciesList.addEventListener("input", currenciesListInputChange); | |
function currenciesListInputChange(event) { | |
const isNewBaseCurrency = event.target.closest("li").id!==baseCurrency; | |
if(isNewBaseCurrency) { | |
currenciesList.querySelector(`#${baseCurrency}`).classList.remove("base-currency"); | |
setNewBaseCurrency(event.target.closest("li")); | |
} | |
const newBaseCurrencyAmount = isNaN(event.target.value) ? 0 : Number(event.target.value); | |
if(baseCurrencyAmount!==newBaseCurrencyAmount || isNewBaseCurrency) { | |
baseCurrencyAmount = newBaseCurrencyAmount; | |
const baseCurrencyRate = currencies.find(currency => currency.abbreviation===baseCurrency).rate; | |
currenciesList.querySelectorAll(".currency").forEach(currencyLI => { | |
if(currencyLI.id!==baseCurrency) { | |
const currencyRate = currencies.find(currency => currency.abbreviation===currencyLI.id).rate; | |
const exchangeRate = currencyLI.id===baseCurrency ? 1 : (currencyRate/baseCurrencyRate).toFixed(4); | |
currencyLI.querySelector(".input input").value = exchangeRate*baseCurrencyAmount!==0 ? (exchangeRate*baseCurrencyAmount).toFixed(4) : ""; | |
} | |
}); | |
} | |
} | |
currenciesList.addEventListener("focusout", currenciesListFocusOut); | |
function currenciesListFocusOut(event) { | |
const inputValue = event.target.value; | |
if(isNaN(inputValue) || Number(inputValue)===0) event.target.value=""; | |
else event.target.value = Number(inputValue).toFixed(4); | |
} | |
currenciesList.addEventListener("keydown", currenciesListKeyDown); | |
function currenciesListKeyDown(event) { | |
if(event.key==="Enter") event.target.blur(); | |
} | |
// Auxiliary Functions | |
function populateAddCyrrencyList() { | |
for(let i=0; i<currencies.length; i++) { | |
addCurrencyList.insertAdjacentHTML( | |
"beforeend", | |
`<li data-currency=${currencies[i].abbreviation}> | |
<img src=${currencies[i].flagURL} class="flag"><span>${currencies[i].abbreviation} - ${currencies[i].name}</span> | |
</li>` | |
); | |
} | |
} | |
function populateCurrenciesList() { | |
for(let i=0; i<initiallyDisplayedCurrencies.length; i++) { | |
const currency = currencies.find(c => c.abbreviation===initiallyDisplayedCurrencies[i]); | |
if(currency) newCurrenciesListItem(currency); | |
} | |
} | |
function newCurrenciesListItem(currency) { | |
if(currenciesList.childElementCount===0) { | |
baseCurrency = currency.abbreviation; | |
baseCurrencyAmount = 0; | |
} | |
addCurrencyList.querySelector(`[data-currency=${currency.abbreviation}]`).classList.add("disabled"); | |
const baseCurrencyRate = currencies.find(c => c.abbreviation===baseCurrency).rate; | |
const exchangeRate = currency.abbreviation===baseCurrency ? 1 : (currency.rate/baseCurrencyRate).toFixed(4); | |
const inputValue = baseCurrencyAmount ? (baseCurrencyAmount*exchangeRate).toFixed(4) : ""; | |
currenciesList.insertAdjacentHTML( | |
"beforeend", | |
`<li class="currency ${currency.abbreviation===baseCurrency ? "base-currency" : ""}" id=${currency.abbreviation}> | |
<img src=${currency.flagURL} class="flag"> | |
<div class="info"> | |
<p class="input"><span class="currency-symbol">${currency.symbol}</span><input placeholder="0.0000" value=${inputValue}></p> | |
<p class="currency-name">${currency.abbreviation} - ${currency.name}</p> | |
<p class="base-currency-rate">1 ${baseCurrency} = ${exchangeRate} ${currency.abbreviation}</p> | |
</div> | |
<span class="close">×</span> | |
</li>` | |
); | |
} | |
fetch(dataURL) | |
.then(res => res.json()) | |
.then(data => { | |
document.querySelector(".date").textContent = data.date; | |
data.rates["EUR"] = 1; | |
currencies = currencies.filter(currency => data.rates[currency.abbreviation]); | |
currencies.forEach(currency => currency.rate = data.rates[currency.abbreviation]); | |
populateAddCyrrencyList(); | |
populateCurrenciesList(); | |
}) | |
.catch(err => console.log(err)); |
@import url('https://fonts.googleapis.com/css?family=Montserrat'); | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
html { | |
font-size: 16px; | |
} | |
body { | |
font-family: 'Montserrat', sans-serif; | |
background-color: #505562; | |
color: #fff; | |
} | |
.container { | |
width: 500px; | |
margin: 20px auto; | |
user-select: none; | |
overflow-x: hidden; | |
position: relative; | |
} | |
.header { | |
background-color: #2d2d37; | |
text-align: center; | |
padding: 1.75rem; | |
} | |
.header h1 { | |
font-size: 2.25rem; | |
} | |
.date { | |
background-color: #222; | |
text-align: right; | |
font-size: 0.75rem; | |
padding: 0.75rem 2rem 0.75rem 0; | |
} | |
ul.currencies { | |
height: calc(100vh - 40px - 100px - 40px - 58px); | |
background-color: #222; | |
padding: 0 1.5rem 1rem 1.5rem; | |
overflow-y: auto; | |
} | |
ul.currencies li { | |
background-color: #2d2d37; | |
border-radius: 5px; | |
list-style: none; | |
padding: 1rem 1rem 0.75rem 1rem; | |
margin-bottom: 1rem; | |
position: relative; | |
} | |
ul.currencies li:last-child { | |
margin-bottom: 0; | |
} | |
ul.currencies li.base-currency { | |
background-color: #264d73; | |
} | |
.flag { | |
width: 60px; | |
height: 40px; | |
border: 1px solid #fff; | |
vertical-align: top; | |
} | |
.info { | |
display: inline-block; | |
width: 78%; | |
} | |
.info .input span { | |
font-size: 1.5rem; | |
display: inline-block; | |
width: 4rem; | |
text-align: center; | |
} | |
.info .input input { | |
font-size: 1.5rem; | |
width: 78%; | |
background-color: transparent; | |
border: 2px solid #fff; | |
border-radius: 5px; | |
color: #fff; | |
padding: 0.3rem; | |
margin-bottom: 0.75rem; | |
} | |
.info .currency-name { | |
font-size: 1rem; | |
font-weight: bold; | |
margin-bottom: 0.5rem; | |
margin-left: 4rem; | |
} | |
.info .base-currency-rate { | |
font-size: 0.8rem; | |
margin-left: 4rem; | |
} | |
ul.currencies li .close { | |
position: absolute; | |
top: 0; | |
right: 0; | |
padding: 0 0.5rem; | |
font-size: 1.5rem; | |
color: #666; | |
cursor: pointer; | |
} | |
ul.currencies li .close:hover { | |
color: #fff; | |
} | |
/* Scrollbar */ | |
ul.currencies::-webkit-scrollbar { | |
width: 5px; | |
} | |
ul.currencies::-webkit-scrollbar-thumb { | |
background-color: #2d2d37; | |
border-bottom: 1rem solid #222; | |
} | |
ul.add-currency-list { | |
position: absolute; | |
bottom: 54px; | |
left: 105%; | |
background-color: #f1f1f1; | |
color: #333; | |
width: 100%; | |
height: calc(100vh - 40px - 100px - 55px); | |
overflow-y: auto; | |
transition: all 0.25s; | |
} | |
ul.add-currency-list li { | |
list-style: none; | |
padding: 0.75rem; | |
border-bottom: 1px solid #ddd; | |
} | |
ul.add-currency-list li.disabled { | |
opacity: 0.7; | |
cursor: not-allowed; | |
} | |
ul.add-currency-list li .flag { | |
width: 3rem; | |
height: 2rem; | |
vertical-align: middle; | |
} | |
ul.add-currency-list li span { | |
margin-left: 1rem; | |
font-weight: bold; | |
} | |
.add-currency-btn { | |
background-color: #00b386; | |
color: #fff; | |
padding: 1rem; | |
font-size: 1.2rem; | |
font-weight: bold; | |
border: none; | |
border-top: 3px solid #222; | |
outline: none; | |
width: 100%; | |
cursor: pointer; | |
transition: background-color 0.25s; | |
position: relative; | |
} | |
.add-currency-btn i { | |
position: absolute; | |
top: 0.6rem; | |
left: 30%; | |
font-size: 2rem; | |
opacity: 0; | |
transition: all 0.25s; | |
} | |
.add-currency-btn.open { | |
background-color: #d9534f; | |
} | |
.add-currency-btn.open i { | |
opacity: 1; | |
left: 1.25rem; | |
} | |
.add-currency-btn.open + ul.add-currency-list { | |
left: 0; | |
} | |
.add-currency-btn.open + ul.add-currency-list li:hover { | |
background-color: #ddd; | |
} | |
@media (max-width: 600px) { | |
html { font-size: 14px; } | |
.container { width: 100%; margin: 0 auto; } | |
ul.currencies { height: calc(100vh - 83px - 34px - 51px); } | |
.header h1 { font-size: 2rem; } | |
.flag { width: 3rem; height: 2rem; } | |
.info .input span { font-size: 1.25rem; width: 3.5rem; } | |
.info .input input { font-size: 1.25rem; width: 76%;} | |
.info .currency-name { margin-left: 3.5rem; } | |
.info .base-currency-rate { margin-left: 3.5rem; } | |
ul.add-currency-list { bottom: 48px; height: calc(100vh - 80px - 51px); } | |
.add-currency-btn i { left: 25%; top:0.65rem; } | |
} |