Cripto Dashboard em Javascript

Introdução
Neste tutorial, você aprenderá a criar um Cripto Dashboard utilizando apenas HTML, CSS e JavaScript, sem a necessidade de bibliotecas externas.
Este guia prático ensina como visualizar informações sobre a cotação de criptomoedas de forma simples e eficiente. Utilizaremos a API do Mercado Bitcoin para consultar e exibir os dados no dashboard.
Cripto Dashboard em Javascript
Primeiro passo você deverá criar a estrutura da página, para isso crie um arquivo html contendo o seguinte código :
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
<title>Cripto DashBoard</title>
</head>
<body>
<script>
</script>
</body>
</html>
Logo após crio a estrutura do conversor:
<div class="container">
<div class="header">
<div class="title-container">
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" stroke="currentColor" fill="currentColor"
width="1em" height="1em" viewBox="0 0 30 30" alt="Ícone de Criptomoeda" >
<path
d="M 15 3 C 8.373 3 3 8.373 3 15 C 3 21.627 8.373 27 15 27 C 21.627 27 27 21.627 27 15 C 27 8.373 21.627 3 15 3 z M 14 8 L 16 8 L 16 10.007812 C 17.86 10.050812 18.970703 10.984141 18.970703 12.494141 C 18.970703 13.554141 18.188109 14.476906 17.162109 14.628906 L 17.162109 14.753906 C 18.486109 14.850906 19.449219 15.849672 19.449219 17.138672 C 19.449219 18.888672 18.129 19.995047 16 19.998047 L 16 22 L 14 22 L 14 20 L 11.5 20 L 11.5 10 L 14 10 L 14 8 z M 13.59375 11.601562 L 13.59375 14.144531 L 15.166016 14.144531 C 16.296016 14.144531 16.912109 13.680953 16.912109 12.876953 C 16.912109 12.079953 16.337844 11.601563 15.339844 11.601562 L 13.59375 11.601562 z M 13.59375 15.558594 L 13.59375 18.398438 L 15.457031 18.398438 C 16.663031 18.398438 17.314453 17.892031 17.314453 16.957031 C 17.314453 16.042031 16.641203 15.558594 15.408203 15.558594 L 13.59375 15.558594 z">
</path>
</svg>
<h2><span>C</span>ripto <span>D</span>ashboard</h2>
</div>
<div class="search-input">
<input type="text" id="filter-input" placeholder="Selecione uma Cripto Moeda. ex: Bitcoin" />
<div class="select-dropdown">
<ul id="optionsList">
<li>Bitcoin</li>
<li>Ethereum</li>
<li>Ripple</li>
</ul>
</div>
<button id="searchButton">
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em"
width="1em" xmlns="http://www.w3.org/2000/svg" alt="Ícone de search">
<path
d="M505 442.7L405.3 343c-4.5-4.5-10.6-7-17-7H372c27.6-35.3 44-79.7 44-128C416 93.1 322.9 0 208 0S0 93.1 0 208s93.1 208 208 208c48.3 0 92.7-16.4 128-44v16.3c0 6.4 2.5 12.5 7 17l99.7 99.7c9.4 9.4 24.6 9.4 33.9 0l28.3-28.3c9.4-9.4 9.4-24.6.1-34zM208 336c-70.7 0-128-57.2-128-128 0-70.7 57.2-128 128-128 70.7 0 128 57.2 128 128 0 70.7-57.2 128-128 128z">
</path>
</svg>
</button>
</div>
</div>
<div class="dashBoard" id="dashBoard">
<div id="loading" style="display: none;">Carregando...</div>
<div class="card">
<div class="css-text-mask mask-one">Bitcoin</div>
<p>Ativo Selecionado</p>
</div>
<div class="card">
<div class="css-text-mask mask-two">BTC</div>
<p>Ticker</p>
</div>
<div class="card">
<div class="css-text-mask mask-three">385M</div>
<p>Última cotacação em (R$)</p>
</div>
<div class="card">
<div class="css-text-mask mask-three">Cripto</div>
<p>Segmento </p>
</div>
<div class="card">
<div class="css-text-mask mask-four">+0,82%</div>
<p>Variação em 24H</p>
</div>
<div class="card">
<div class="css-text-mask mask-one">6,9B</div>
<p>Capitalização de mercado</p>
</div>
</div>
</div>
<div id="loadingModal" class="modal">
<div class="modal-content">
<div class="loader"></div>
<p>Carregando...</p>
</div>
</div>
<script src="main.js"></script>
No código acima crio a interface do DashBoard Cripto.
Adicionei um campo para entrada de um número e um botão para executar a consulta a api de cotação da moeda e por fim adicionei uma área para exibir o resultado retornado pela api.
Depois adiciono o css responsável pela estilização da página :
*,
*:after,
*:before {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
vertical-align: baseline;
text-decoration: none;
}
:root {
--white: #ffffff;
--dark: rgb(40, 44, 51);
--green: rgb(166, 247, 80);
--green2: rgb(142, 211, 69);
--gray: rgb(85, 89, 95);
--orange: rgb(255, 216, 98);
--red: #f46663;
--contorno: rgba(255, 255, 255, 0.7);
}
body {
height: 100vh;
width: 100vw;
display: flex;
align-items: center;
justify-content: center;
background-color: #121f32;
color: var(--white);
font-family: basic-sans, sans-serif !important;
}
.container {
width: calc(100% - 2rem);
padding: 1rem;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.header {
width: calc(80% - 2rem);
padding: 1rem;
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.title-container {
display: flex;
flex-direction: row;
align-items: end;
gap: 0.5rem;
}
.title-container > svg {
width: 5rem;
height: 5rem;
fill: var(--dark);
stroke: var(--green);
}
.title-container h2 {
font-size: 3rem;
line-height: 4rem;
font-weight: 700;
font-style: normal;
color: var(--white);
}
.title-container h2 > span {
font-size: 3.8rem;
line-height: 4rem;
color: var(--green);
}
.search-input {
min-width: 30%;
display: flex;
gap: 0.5rem;
align-items: center;
position: relative;
padding: 0.5rem;
background: var(--dark);
border: 1px solid var(--gray);
border-radius: 4px;
}
.search-input input {
width: 100%;
font-size: 1rem;
color: var(--green);
position: relative;
outline: none;
padding: 0.5rem 1rem;
background: var(--dark);
border: 1px solid transparent;
}
.search-input .select-dropdown {
position: absolute;
top: 50px;
left: 0;
background: var(--dark);
border: 1px solid var(--gray);
color: var(--green);
width: 250px;
z-index: 1;
display: none;
}
.search-input .select-dropdown ul {
list-style-type: none;
padding: 0;
margin: 0;
}
.search-input .select-dropdown li {
padding: 0.5rem;
cursor: pointer;
}
.search-input .select-dropdown li:hover {
background-color: var(--green);
color: var(--dark);
}
.search-input button {
padding: 0.8rem;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
text-decoration: none;
background-color: var(--green);
}
.search-input button:hover {
background-color: var(--green2);
scale: 0.9;
}
.search-input button > svg {
font-size: 1rem;
fill: var(--gray);
}
.dashBoard {
width: calc(80% - 2rem);
padding: 1rem;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.card {
display: flex;
flex-direction: column;
gap: 0.3rem;
padding: 0.5rem;
}
.card p {
font-size: 0.9rem;
line-height: 1rem;
font-weight: 600;
letter-spacing: 0.0605rem;
color: var(--orange);
}
.css-text-mask {
text-align: left;
font-family: basic-sans, sans-serif;
font-size: 5rem;
line-height: 5rem;
font-weight: 800;
background-position: 0px 0px;
animation: animatedBackground 60s linear infinite normal;
background-clip: text;
-webkit-text-fill-color: #0000;
background-size: 50%;
-webkit-text-stroke-width: 1px;
-webkit-text-stroke-color: #ffffff;
}
.css-text-mask {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mask-one {
background-image: url(cripto-azul.jpg);
}
.mask-two {
background-image: url(cripto-dourado.jpg);
}
.mask-three {
background-image: url(cripto-verde.webp);
text-transform: lowercase;
}
.mask-four {
background-image: url(cripto-roxo.webp);
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
justify-content: center;
align-items: center;
}
.modal-content {
padding: 1rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.4rem;
border: 2px solid var(--gray);
border-radius: 4px;
background: var(--dark);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
color: var(--green);
font-size: 0.8rem;
font-weight: 600;
letter-spacing: 0.125rem;
}
.loader {
border: 8px solid var(--gray);
border-top: 8px solid var(--green);
border-radius: 50%;
width: 3rem;
height: 3rem;
animation: loader-spin 1s linear infinite;
}
@keyframes loader-spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes animatedBackground {
0% {
background-position: 0 0;
}
100% {
background-position: 500px 0;
}
}
@keyframes float {
0% {
transform: translateY(0);
}
50% {
transform: translateY(-10px); /* Move para cima */
}
100% {
transform: translateY(0);
}
}
@media (max-width: 1800px) {
.css-text-mask {
font-size: 4.2rem;
}
}
@media (max-width: 1020px) {
.css-text-mask {
font-size: 3rem;
}
}
@media (max-width: 768px) {
.css-text-mask {
font-size: 1.2rem;
}
}
@media (max-width: 480px) {
.css-text-mask {
font-size: 1rem;
}
}
Por ultimo adiciono o script que irá obter a cotação da moeda:
const input = document.getElementById("filter-input");
const dropdown = document.querySelector(".select-dropdown");
const optionsList = document.getElementById("optionsList");
const searchButton = document.getElementById("searchButton");
const dashBoard = document.getElementById("dashBoard");
const loadingModal = document.getElementById("loadingModal");
let selectedCripto = null;
const optionsCripto = [
{ label: "Bitcoin", value: "BTC" },
{ label: "Ethereum", value: "ETH" },
{ label: "Litecoin", value: "LTC" },
{ label: "Ripple", value: "XRP" },
{ label: "Cardano", value: "ADA" },
];
function filterOptions() {
const filter = input.value.toLowerCase();
const options = optionsList.getElementsByTagName("li");
let hasVisibleOption = false;
Array.from(options).forEach((option) => {
const optionText = option.textContent || option.innerText;
if (optionText.toLowerCase().includes(filter)) {
option.style.display = "";
hasVisibleOption = true;
} else {
option.style.display = "none";
}
});
toggleDropdown(hasVisibleOption);
}
function selectOption(optionText, optionValue) {
input.value = optionText;
selectedCripto = optionValue;
toggleDropdown(false);
}
function toggleDropdown(show) {
dropdown.style.display = show ? "block" : "none";
}
// Eventos
// Adiciona o evento global de clique para fechar o dropdown ao clicar fora
document.addEventListener("click", function (event) {
if (!input.contains(event.target) && !dropdown.contains(event.target)) {
toggleDropdown(false); // Fecha o dropdown
}
});
// Adiciona evento para filtrar opções
input.addEventListener("input", filterOptions);
// Exibe o dropdown ao focar no input
input.addEventListener("focus", () => toggleDropdown(true));
// Adiciona evento de clique para cada opção da lista
optionsList.addEventListener("click", (event) => {
if (event.target.tagName === "LI") {
const optionText = event.target.textContent || event.target.innerText;
const optionValue = event.target.dataset.value;
selectOption(optionText, optionValue);
}
});
searchButton.addEventListener("click", async () => {
showLoading();
if (selectedCripto) {
const criptoInfo = await getCotacao(selectedCripto);
updateCards(criptoInfo);
} else {
alert("Selecione uma criptomoeda antes de buscar a cotação.");
}
hideLoading();
});
async function loadOptions() {
try {
const optionsHTML = optionsCripto
.map((option) => `<li data-value="${option.value}">${option.label}</li>`)
.join("");
optionsList.innerHTML = optionsHTML;
} catch (error) {
console.error("Erro ao carregar opções:", error);
}
}
loadOptions();
async function fetchData(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Erro ao buscar dados: ${response.status}`);
}
return await response.json();
}
async function getCotacao(cripto) {
try {
const [criptoInfoData, criptoCotacaoData] = await Promise.all([
fetchData(
`https://api.mercadobitcoin.net/api/v4/symbols?symbols=${cripto}-BRL`
),
fetchData(
`https://api.mercadobitcoin.net/api/v4/tickers?symbols=${cripto}-BRL`
),
]);
// Extração e formatação dos dados
const { "base-currency": symbol, description, type } = criptoInfoData;
const { open, last, vol } = criptoCotacaoData[0];
const variation =
((parseFloat(last) - parseFloat(open)) / parseFloat(open)) * 100;
const criptoInfo = {
symbol: symbol[0],
description: description[0],
type: type[0],
lastValue: last,
variation: variation,
volume: vol,
};
// Exibe o resultado no console
console.log("Informações da Criptomoeda:", criptoInfo);
return criptoInfo;
} catch (error) {
console.error("Erro ao buscar dados da criptomoeda:", error);
return null;
}
}
const updateCards = (criptoInfo) => {
const cardData = [
{
label: "Ativo Selecionado",
value: criptoInfo.description,
maskClass: "mask-one",
},
{ label: "Ticker", value: criptoInfo.symbol, maskClass: "mask-two" },
{
label: "Última cotação em (R$)",
value: formatNumber(criptoInfo.lastValue),
maskClass: "mask-three",
},
{ label: "Segmento", value: criptoInfo.type, maskClass: "mask-three" },
{
label: "Variação em 24H",
value: formatPercent(criptoInfo.variation),
maskClass: "mask-four",
},
{
label: "Volume Movimentado",
value: formatNumber(criptoInfo.volume),
maskClass: "mask-one",
},
];
const dashBoardCards = cardData
.map((data) => createCard(data.label, data.value, data.maskClass))
.join("");
dashBoard.innerHTML = dashBoardCards;
};
const formatNumber = (number) => {
number = Number(number);
if (number >= 1e9) {
return (number / 1e9).toFixed(0) + "B"; // Bilhões
} else if (number >= 1e6) {
return (number / 1e6).toFixed(0) + "M"; // Milhões
} else if (number >= 1e3) {
return (number / 1e3).toFixed(0) + "K"; // Milhar
}
return formatNumberPTBR(number);
};
const formatPercent = (number) => {
return `${formatNumberPTBR(number)}%`;
};
const formatNumberPTBR = (number) => {
const formatter = new Intl.NumberFormat("pt-BR", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
});
return formatter.format(number);
};
const createCard = (label, value, maskClass) => {
return `
<div class="card">
<div class="css-text-mask ${maskClass}">${value}</div>
<p>${label}</p>
</div>
`;
};
const showLoading = () => {
loadingModal.style.display = "flex";
};
const hideLoading = () => {
loadingModal.style.display = "none";
};
Você pode visualizar o componente no link abaixo:
Deixo aqui um vídeo curto do processo de criação do DashBoard:
Conclusão
Com esses simples passos, você consegue de forma rápida criar um Cripto Dashboard, usando apenas HTML e CSS. Caso necessite você pode personalizar estilizar conforme a sua necessidade.
Código fonte do tutorial do Dashboard em Javascript:
Referências