
🧮 Calculadora simples em JavaScript: guia passo a passo
Precisa de uma calculadora simples no seu site, uma calculadora de portes de envio, de crédito habitação ou apenas «calcular algo na hora»? Escrever um backend para isto é como usar um canhão para matar pardais. O JavaScript no lado do cliente resolve o assunto numa noite.
O problema com a maioria dos guias: dão-lhe uma versão em jQuery de 2017 com eval(), e isso é um buraco de segurança que não lhe mencionam. Vamos seguir um caminho diferente: construir uma calculadora em JavaScript puro, com um layout adequado e sem a agenda de «instalar três dependências».
No final deste artigo, terá código funcional que soma, subtrai, multiplica e divide, e uma compreensão do motivo pelo qual é melhor evitar o eval().
💡 Visão geral rápida:
- HTML: construir a marcação da calculadora com CSS Grid, sem tabela e sem center
- CSS: adicionar grelha responsiva, estilos dos botões e visor
- JavaScript: escrever captura de cliques, recolha da expressão e cálculo sem eval()
- Segurança: aprender pelo que substituir o eval() e porque é que isso importa
Passo 1: HTML, marcação da calculadora
Primeiro, construímos o esqueleto. Nada de <center> e <table border="4"> de 2005, usamos CSS Grid: quatro colunas para dígitos e operadores, mais uma linha de visor no topo.
Crie um ficheiro index.html e adicione esta marcação:
1 <div class="calculator"> 2 <input class="display" type="text" readonly> 3 4 <div class="buttons"> 5 <button class="btn-clear" data-action="clear">C</button> 6 <button class="btn-operator" data-action="operator" data-value="/">/</button> 7 <button class="btn-operator" data-action="operator" data-value="*">*</button> 8 <button class="btn-operator" data-action="operator" data-value="-">-</button> 9 10 <button class="btn-number" data-action="number" data-value="7">7</button> 11 <button class="btn-number" data-action="number" data-value="8">8</button> 12 <button class="btn-number" data-action="number" data-value="9">9</button> 13 <button class="btn-operator" data-action="operator" data-value="+">+</button> 14 15 <button class="btn-number" data-action="number" data-value="4">4</button> 16 <button class="btn-number" data-action="number" data-value="5">5</button> 17 <button class="btn-number" data-action="number" data-value="6">6</button> 18 <button class="btn-equals" data-action="calculate">=</button> 19 20 <button class="btn-number" data-action="number" data-value="1">1</button> 21 <button class="btn-number" data-action="number" data-value="2">2</button> 22 <button class="btn-number" data-action="number" data-value="3">3</button> 23 <button class="btn-number btn-zero" data-action="number" data-value="0">0</button> 24 </div> 25 </div>
Aqui o visor (<input>) é só de leitura: o utilizador não introduz dados pelo teclado, mas clica em botões. Isto protege contra caracteres inválidos na expressão. Os botões estão divididos por atributos data-action: "number", "operator", "calculate" e "clear", para que o script saiba o que fazer com cada clique. O atributo data-value guarda o valor do botão: um dígito ou sinal de operação.
Nota: o visor tem o atributo readonly, o utilizador não conseguirá escrever alert('hack') ali a partir do teclado.
Passo 2: CSS, aparência
Agora os estilos. Sem CSS, a calculadora parece uma pilha de botões; a grelha resolve tudo. No mesmo index.html, dentro do <head>, adicione:
1 <style> 2 .calculator { 3 max-width: 320px; 4 margin: 0 auto; 5 font-family: 'Segoe UI', sans-serif; 6 } 7 8 .display { 9 width: 100%; 10 padding: 16px 12px; 11 font-size: 2rem; 12 text-align: right; 13 border: 2px solid #ccc; 14 border-radius: 6px; 15 margin-bottom: 10px; 16 box-sizing: border-box; 17 background: #f9f9f9; 18 } 19 20 .buttons { 21 display: grid; 22 grid-template-columns: repeat(4, 1fr); 23 gap: 6px; 24 } 25 26 .buttons button { 27 padding: 18px 0; 28 font-size: 1.3rem; 29 border: none; 30 border-radius: 6px; 31 cursor: pointer; 32 background: #e9ecef; 33 transition: background 0.15s; 34 } 35 36 .buttons button:hover { 37 background: #dee2e6; 38 } 39 40 .btn-operator { 41 background: #ff922b !important; 42 color: #fff; 43 } 44 45 .btn-operator:hover { 46 background: #f76707 !important; 47 } 48 49 .btn-equals { 50 background: #228be6 !important; 51 color: #fff; 52 } 53 54 .btn-equals:hover { 55 background: #1971c2 !important; 56 } 57 58 .btn-clear { 59 background: #fa5252 !important; 60 color: #fff; 61 } 62 63 .btn-clear:hover { 64 background: #e03131 !important; 65 } 66 67 .btn-zero { 68 grid-column: span 2; 69 } 70 </style>
A grelha grid-template-columns: repeat(4, 1fr) proporciona um teclado uniforme de quatro colunas, os botões distribuem-se sozinhos. O botão "0" é esticado por duas colunas com grid-column: span 2, uma técnica padrão para o layout de calculadoras.
As cores aqui são funcionais: laranja para operadores, azul para "igual", vermelho para limpar. O utilizador lê instantaneamente o propósito do botão antes de ler o texto.
Passo 3: JavaScript, lógica sem eval()
A parte principal. Vamos ligar o script no final do <body> no mesmo index.html:
1 <script> 2 const display = document.querySelector('.display'); 3 4 let expression = ''; 5 6 document.querySelector('.buttons').addEventListener('click', function (e) { 7 const btn = e.target.closest('button'); 8 if (!btn) return; 9 10 const action = btn.dataset.action; 11 12 if (action === 'number') { 13 expression += btn.dataset.value; 14 display.value = expression; 15 } 16 17 if (action === 'operator') { 18 if (expression === '') return; 19 const last = expression.slice(-1); 20 if (['+', '-', '*', '/'].includes(last)) return; 21 expression += btn.dataset.value; 22 display.value = expression; 23 } 24 25 if (action === 'calculate') { 26 if (expression === '') return; 27 const result = safeCalculate(expression); 28 display.value = result; 29 expression = String(result); 30 } 31 32 if (action === 'clear') { 33 expression = ''; 34 display.value = ''; 35 } 36 }); 37 38 function safeCalculate(expr) { 39 try { 40 const fn = new Function('return ' + expr); 41 const result = fn(); 42 if (typeof result !== 'number' || !isFinite(result)) { 43 return 'Error'; 44 } 45 return Math.round(result * 1e10) / 1e10; 46 } catch (e) { 47 return 'Error'; 48 } 49 } 50 </script>
Vamos decompor o que acontece aqui, linha a linha.
Captura de cliques. O addEventListener não está associado a cada botão, mas ao contentor .buttons; isto é delegação de eventos. Ao clicar, e.target.closest('button') encontra o botão mais próximo (mesmo que tenha clicado no texto dentro dele). btn.dataset.action lê o atributo data-action e depois a lógica ramifica.
Recolha da expressão. Cada pressão de um dígito acrescenta-o à string expression. Um operador é acrescentado apenas se a expressão não estiver vazia e o último caractere não for um operador (proteção contra ++ e */ duplos). O visor atualiza a cada pressão, o utilizador vê a string completa, como numa calculadora real.
Cálculo. Em vez de eval(), o construtor new Function(). Ele também executa uma string como código, mas com uma diferença: o eval() tem acesso ao escopo local, enquanto o Function só tem acesso ao escopo global. Isto não é uma bala de prata, mas para uma calculadora do lado do cliente onde o utilizador introduz a expressão por si mesmo, é um nível de isolamento suficiente. Após o cálculo, o resultado é arredondado a 10 casas decimais, caso contrário 0.1 + 0.2 transforma-se em 0.30000000000000004.
Limpar. O botão "C" repõe a zero tanto a expression como o visor.
Proteção contra erros. Se a expressão estiver vazia, calculate e operator são ignorados silenciosamente. A divisão por zero dá Infinity, o isFinite() deteta isto e apresenta "Erro".
Passo 4: juntar tudo
Vamos combinar as três partes num só ficheiro. Aqui está o index.html completo que pode ser aberto num navegador:
1 <!DOCTYPE html> 2 <html lang="ru"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>JavaScript Calculator</title> 7 <style> 8 .calculator { 9 max-width: 320px; 10 margin: 40px auto; 11 font-family: 'Segoe UI', sans-serif; 12 } 13 .display { 14 width: 100%; 15 padding: 16px 12px; 16 font-size: 2rem; 17 text-align: right; 18 border: 2px solid #ccc; 19 border-radius: 6px; 20 margin-bottom: 10px; 21 box-sizing: border-box; 22 background: #f9f9f9; 23 } 24 .buttons { 25 display: grid; 26 grid-template-columns: repeat(4, 1fr); 27 gap: 6px; 28 } 29 .buttons button { 30 padding: 18px 0; 31 font-size: 1.3rem; 32 border: none; 33 border-radius: 6px; 34 cursor: pointer; 35 background: #e9ecef; 36 transition: background 0.15s; 37 } 38 .buttons button:hover { background: #dee2e6; } 39 .btn-operator { background: #ff922b !important; color: #fff; } 40 .btn-operator:hover { background: #f76707 !important; } 41 .btn-equals { background: #228be6 !important; color: #fff; } 42 .btn-equals:hover { background: #1971c2 !important; } 43 .btn-clear { background: #fa5252 !important; color: #fff; } 44 .btn-clear:hover { background: #e03131 !important; } 45 .btn-zero { grid-column: span 2; } 46 </style> 47 </head> 48 <body> 49 <div class="calculator"> 50 <input class="display" type="text" readonly> 51 <div class="buttons"> 52 <button class="btn-clear" data-action="clear">C</button> 53 <button class="btn-operator" data-action="operator" data-value="/">/</button> 54 <button class="btn-operator" data-action="operator" data-value="*">*</button> 55 <button class="btn-operator" data-action="operator" data-value="-">-</button> 56 <button class="btn-number" data-action="number" data-value="7">7</button> 57 <button class="btn-number" data-action="number" data-value="8">8</button> 58 <button class="btn-number" data-action="number" data-value="9">9</button> 59 <button class="btn-operator" data-action="operator" data-value="+">+</button> 60 <button class="btn-number" data-action="number" data-value="4">4</button> 61 <button class="btn-number" data-action="number" data-value="5">5</button> 62 <button class="btn-number" data-action="number" data-value="6">6</button> 63 <button class="btn-equals" data-action="calculate">=</button> 64 <button class="btn-number" data-action="number" data-value="1">1</button> 65 <button class="btn-number" data-action="number" data-value="2">2</button> 66 <button class="btn-number" data-action="number" data-value="3">3</button> 67 <button class="btn-number btn-zero" data-action="number" data-value="0">0</button> 68 </div> 69 </div> 70 71 <script> 72 const display = document.querySelector('.display'); 73 let expression = ''; 74 document.querySelector('.buttons').addEventListener('click', function (e) { 75 const btn = e.target.closest('button'); 76 if (!btn) return; 77 const action = btn.dataset.action; 78 if (action === 'number') { 79 expression += btn.dataset.value; 80 display.value = expression; 81 } 82 if (action === 'operator') { 83 if (expression === '') return; 84 const last = expression.slice(-1); 85 if (['+', '-', '*', '/'].includes(last)) return; 86 expression += btn.dataset.value; 87 display.value = expression; 88 } 89 if (action === 'calculate') { 90 if (expression === '') return; 91 const result = safeCalculate(expression); 92 display.value = result; 93 expression = String(result); 94 } 95 if (action === 'clear') { 96 expression = ''; 97 display.value = ''; 98 } 99 }); 100 function safeCalculate(expr) { 101 try { 102 const fn = new Function('return ' + expr); 103 const result = fn(); 104 if (typeof result !== 'number' || !isFinite(result)) return 'Error'; 105 return Math.round(result * 1e10) / 1e10; 106 } catch (e) { 107 return 'Error'; 108 } 109 } 110 </script> 111 </body> 112 </html>
Guarde-o, abra num navegador, a calculadora funciona de imediato, sem servidor, sem npm install. Na prática, este código é suficiente para uma calculadora de portes de envio numa landing page ou um formulário de cálculo de desconto.

Porque não usar eval(): segurança que os guias para principiantes não mencionam
Se já leu tutoriais antigos, provavelmente viu eval(expression). Sim, funciona. Mas o problema é que o eval() executa qualquer código JavaScript passado como string. Se alert(document.cookie) chegar ao visor, será executado. E se o visor estiver aberto para introdução manual, pode inserir-se algo mais grave.
No nosso caso, o visor funciona em modo readonly, a expressão é montada apenas clicando em botões. O risco é mínimo. Mas o hábito de usar eval() é mau: um dia vai copiar este padrão para um formulário com entrada do utilizador e obter um buraco de XSS.
Existem três alternativas, por ordem crescente de complexidade:
Abordagem | Proteção | Complexidade |
|---|---|---|
| Não vê variáveis locais, apenas globais | Mínima |
Analisador manual (parse da string por operadores) | Controlo total sobre a expressão | Média |
Web Worker com contexto isolado | A expressão não toca no DOM de todo | Elevada |
Para uma calculadora educativa e a maioria dos cenários reais, new Function() com verificação do resultado é suficiente. Mas se estiver a incorporar uma calculadora num formulário de e-commerce (preço × quantidade), nem se preocupe com o parse de strings: obtenha os valores dos campos e calcule diretamente: priceInput.value * qtyInput.value. Aí, não é necessário qualquer eval em primeiro lugar.
Como expandir: entrada por teclado e cadeias de operações
Quando a versão básica estiver a funcionar, adicione duas funcionalidades que transformam um artefacto numa ferramenta:
Entrada por teclado. Associe addEventListener('keydown', ...) ao document e mapeie as teclas para os botões: dígitos e ponto para data-action="number", Enter para "igual", Escape para "C", +-*/ para operadores. Ignore letras e caracteres especiais.
Cadeias de operações. Neste momento, a nossa calculadora calcula «tudo de uma vez». Mas se pressionar 5 + 3 = e depois * 2, não continua, a expressão já foi reiniciada para o resultado. Isto resolve-se facilmente: depois do = não sobrescreva a expression, mas guarde o último resultado numa variável separada lastResult. No operador seguinte, inicie a expressão com lastResult.
Ambas as melhorias são 15 a 20 linhas de código cada. Experimente-as você mesmo como exercício: é a melhor forma de compreender que uma calculadora não é um «projeto educativo», mas uma mini-tarefa normal de engenharia.
⁉️🤔 Perguntas frequentes
É possível fazer sem JavaScript de todo?
Para quatro operações aritméticas no cliente, não. HTML e CSS são marcação e estilos, não fazem cálculos. O cálculo do lado do servidor via PHP significa recarregar a página a cada pressão de "=". O JavaScript aqui é a única opção prática. Existem truques de CSS com
calc()e checkboxes a imitar lógica, mas isso é uma atração, não uma calculadora: no máximo uma operação, sem limpar, com código péssimo.
Como é que esta abordagem difere de «usar um pacote npm pronto»?
Nem uma única dependência.
npm install calculator-libarrasta mais 40 pacotes, um bundler e meio megabyte de bundle, para somar dois números. A nossa variante: um ficheiro HTML, 90 linhas de JavaScript, abre em qualquer navegador sem servidor. Para a maioria das tarefas típicas, uma calculadora de portes de envio, cálculo de crédito habitação numa landing page, formulário «calcular custo», isto é suficiente.
Porque não jQuery?
jQuery para uma calculadora é como um miniautocarro para ir comprar pão. Em 2017, ainda fazia sentido:
$('.btn')é mais curto quedocument.querySelectorAll('.btn'), e os navegadores comportavam-se de forma diferente. Hoje,addEventListenereclosest()funcionam de igual modo em todos os navegadores, e arrastar 87 KB de jQuery comprimido para cinco manipuladores de clique é excessivo. Se o jQuery já estiver no projeto, use-o, mas para código novo escreva em JS puro.
O que fazer com a vírgula flutuante? 0.1 + 0.2 continua a dar 0.30000000000000004.
No código colocámos
Math.round(result * 1e10) / 1e10, isto remove a cauda para dez dígitos. Para cálculos financeiros (preços, percentagens), use inteiros, calcule em cêntimos, não em euros:(100 + 200) / 100 = 3.00em vez de0.1 + 0.2. Aqui não são necessáriosevalnemFunction, basta obter os números dos campos e somá-los.
Deve escrever a sua própria calculadora ou usar uma já feita?
Se precisa de uma calculadora para uma página, escreva-a você mesmo numa hora usando este guia. O código tem menos de 100 linhas, zero dependências, você controla o comportamento.
Se está a incorporar cálculo no WooCommerce ou num formulário de encomenda, não escreva um analisador de expressões. Obtenha os valores dos campos de entrada e calcule diretamente: total = basePrice + optionsSum. Isto é mais fiável, mais simples e mais seguro do que qualquer cálculo com strings.
O código do artigo é uma base funcional. Altere o layout, adicione percentagens, memória (botões M+/M-/MR), histórico de operações. Escreva uma calculadora uma vez com as suas próprias mãos, e a questão «como é que isto funciona» ficará encerrada para sempre.



