Skip to content

Tudo para WordPress, desenvolvimento web — e não só

🧮 Calculadora simples em JavaScript: guia passo a passo

🧮 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>
2const display = document.querySelector('.display');
3
4let expression = '';
5
6document.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
38function 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.

Aspeto de calculadora em JavaScript puro

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

new Function() (como a nossa)

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-lib arrasta 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 que document.querySelectorAll('.btn'), e os navegadores comportavam-se de forma diferente. Hoje, addEventListener e closest() 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.00 em vez de 0.1 + 0.2. Aqui não são necessários eval nem Function, 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.