Skip to content

Tutto per WordPress, lo sviluppo web — e non solo

🧮 Calcolatrice JavaScript semplice: guida passo passo

🧮 Calcolatrice JavaScript semplice: guida passo passo

Hai bisogno di una semplice calcolatrice sul tuo sito, un calcolatore di spedizione, un calcolatore per mutui o semplicemente di "calcolare qualcosa al volo"? Scrivere un backend per questo è come usare un cannone per sparare ai passeri. JavaScript lato client risolve la cosa in una serata.

Il problema della maggior parte delle guide: ti propongono una versione jQuery del 2017 con eval(), e quella è una falla di sicurezza di cui non ti parlano. Noi seguiremo una strada diversa: costruiremo una calcolatrice in puro JavaScript, con un layout adeguato e senza la fissa di "installare tre dipendenze".

Alla fine di questo articolo avrai un codice funzionante che somma, sottrae, moltiplica e divide, e avrai capito perché è meglio evitare eval().

💡 Panoramica rapida:

  • HTML: costruire il markup della calcolatrice con CSS Grid, niente tabelle e centrature
  • CSS: aggiungere una griglia responsive, lo stile dei pulsanti e il display
  • JavaScript: scrivere la cattura del click, la raccolta dell'espressione e il calcolo senza eval()
  • Sicurezza: capire con cosa sostituire eval() e perché è importante

Step 1: HTML, markup della calcolatrice

Per prima cosa costruiamo lo scheletro. Niente <center> e <table border="4"> del 2005, usiamo CSS Grid: quattro colonne per cifre e operatori più una riga in alto per il display.

Crea un file index.html e aggiungi questo markup:

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>

Qui il display (<input>) è in sola lettura: l'utente non inserisce dati dalla tastiera ma clicca i pulsanti. Questo protegge da caratteri spazzatura nell'espressione. I pulsanti sono distinti dagli attributi data-action, "number", "operator", "calculate" e "clear", così lo script sa cosa fare con un click. L'attributo data-value memorizza il valore del pulsante: una cifra o un segno di operazione.

Nota: il display ha l'attributo readonly, l'utente non potrà digitare alert('hack') dalla tastiera.

Step 2: CSS, aspetto

Ora gli stili. Senza CSS la calcolatrice sembra una pila di pulsanti, la griglia sistema tutto. Nello stesso index.html, dentro <head>, aggiungi:

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>

La griglia grid-template-columns: repeat(4, 1fr) crea una tastiera uniforme di quattro colonne, i pulsanti si distribuiscono da soli. Il pulsante "0" è allungato su due colonne con grid-column: span 2, una tecnica standard per il layout delle calcolatrici.

I colori qui sono funzionali: arancione per gli operatori, blu per "uguale", rosso per il reset. L'utente coglie al volo lo scopo del pulsante prima ancora di leggere il testo.

Step 3: JavaScript, logica senza eval()

La parte principale. Colleghiamo lo script alla fine del <body> nello stesso 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>

Analizziamo cosa succede qui, riga per riga.

Cattura del click. addEventListener non è attaccato a ogni pulsante ma al contenitore .buttons, questa è event delegation. Al click, e.target.closest('button') trova il pulsante più vicino (anche se hai cliccato sul testo al suo interno). btn.dataset.action legge l'attributo data-action, e poi la logica si dirama.

Raccolta dell'espressione. Ogni pressione di una cifra la aggiunge alla stringa expression. Un operatore viene aggiunto solo se l'espressione non è vuota e l'ultimo carattere non è un operatore (protezione contro doppi ++ e */). Il display si aggiorna a ogni pressione, l'utente vede l'intera stringa, come su una calcolatrice vera.

Calcolo. Invece di eval(), usiamo il costruttore new Function(). Anch'esso esegue una stringa come codice, ma con una differenza: eval() ha accesso allo scope locale, mentre Function ha accesso solo allo scope globale. Non è la panacea, ma per una calcolatrice lato client in cui l'espressione la inserisce l'utente stesso è un livello di isolamento sufficiente. Dopo il calcolo, il risultato è arrotondato a 10 cifre decimali, altrimenti 0.1 + 0.2 diventerebbe 0.30000000000000004.

Reset. Il pulsante "C" azzera sia expression che il display.

Protezione dagli errori. Se l'espressione è vuota, calculate e operator vengono ignorati silenziosamente. La divisione per zero dà Infinity, isFinite() lo intercetta e restituisce "Error".

Step 4: mettiamo tutto insieme

Uniamo le tre parti in un unico file. Ecco l'index.html completo che può essere aperto in un browser:

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>

Salvalo, aprilo in un browser, la calcolatrice funziona subito, nessun server, nessun npm install. Nella pratica, questo codice è sufficiente per un calcolatore di spedizione su una landing page o un modulo per il calcolo di uno sconto.

Aspetto di calcolatrice in puro JavaScript

Perché non eval(): sicurezza che le guide per principianti non menzionano

Se hai letto vecchi tutorial, avrai probabilmente visto eval(expression). Sì, funziona. Ma il problema è che eval() esegue qualsiasi codice JavaScript passato come stringa. Se alert(document.cookie) finisce nel display, verrà eseguito. E se il display è aperto all'input manuale, puoi inserirci qualcosa di più serio.

Nel nostro caso, il display funziona in modalità readonly, l'espressione viene composta solo cliccando i pulsanti. Il rischio è minimo. Ma l'abitudine di usare eval() è cattiva: un giorno copierai questo pattern in un modulo con input utente e ti ritroverai con una falla XSS.

Ci sono tre alternative, in ordine crescente di complessità:

Approccio

Protezione

Complessità

new Function() (come nel nostro caso)

Non vede le variabili locali, solo quelle globali

Minima

Parser manuale (analisi della stringa per operatori)

Controllo totale sull'espressione

Media

Web Worker con contesto isolato

L'espressione non tocca affatto il DOM

Alta

Per una calcolatrice didattica e per la maggior parte degli scenari reali, new Function() con verifica del risultato è sufficiente. Ma se stai incorporando una calcolatrice in un modulo e-commerce (prezzo × quantità), non complicarti la vita con il parsing di stringhe: prendi i valori dai campi e calcola direttamente: priceInput.value * qtyInput.value. In quel caso non serve alcun eval già in partenza.

Come estendere: input da tastiera e catene di operazioni

Quando la versione base funziona, aggiungi due funzionalità che trasformano un lavoretto in uno strumento:

Input da tastiera. Attacca addEventListener('keydown', ...) a document e mappa i tasti ai pulsanti: cifre e punto a data-action="number", Enter a "uguale", Escape a "C", +-*/ agli operatori. Ignora lettere e caratteri speciali.

Catene di operazioni. Al momento la nostra calcolatrice calcola "tutto in una volta". Ma se premi 5 + 3 = e poi * 2, non prosegue, l'espressione è già stata azzerata al risultato. Si risolve semplicemente: dopo = non sovrascrivere expression, ma salva l'ultimo risultato in una variabile separata lastResult. Al prossimo operatore, fai partire l'espressione da lastResult.

Entrambi i miglioramenti richiedono 15-20 righe di codice ciascuno. Provali tu stesso come esercizio: è il modo migliore per capire che una calcolatrice non è un "progetto didattico" ma un normale mini-compito di ingegneria.

⁉️🤔 Domande frequenti

Si può fare a meno di JavaScript del tutto?

Per quattro operazioni aritmetiche lato client, no. HTML e CSS sono markup e stili, non fanno calcoli. Il calcolo lato server via PHP significa ricaricare la pagina a ogni pressione di "=". JavaScript qui è l'unica opzione pratica. Esistono trucchi CSS con calc() e checkbox che imitano la logica, ma quella è un'attrazione, non una calcolatrice: al massimo un'operazione, nessun reset, con codice pessimo.

In cosa si differenzia questo approccio dal "prendere un pacchetto npm già pronto"?

Nessuna dipendenza. npm install calculator-lib si tira dietro altri 40 pacchetti, un bundler e mezzo megabyte di bundle, per sommare due numeri. La nostra variante: un file HTML, 90 righe di JavaScript, si apre in qualsiasi browser senza server. Per la maggior parte dei compiti tipici, un calcolatore di spedizione, il calcolo di un mutuo su una landing page, un modulo "calcola costo", questo basta.

Perché non jQuery?

jQuery per una calcolatrice è come un minibus per andare a prendere il pane. Nel 2017 aveva ancora senso: $('.btn') è più corto di document.querySelectorAll('.btn'), e i browser si comportavano in modo diverso. Oggi addEventListener e closest() funzionano allo stesso modo in tutti i browser, e trascinarsi 87 KB di jQuery compresso per cinque gestori di click è eccessivo. Se jQuery è già nel progetto, usalo, ma per codice nuovo scrivi in JS puro.

Cosa fare con la virgola mobile? 0.1 + 0.2 dà ancora 0.30000000000000004.

Nel codice abbiamo messo Math.round(result * 1e10) / 1e10, questo elimina la coda per dieci cifre. Per calcoli finanziari (prezzi, percentuali), usa numeri interi, calcola in centesimi, non in euro: (100 + 200) / 100 = 3.00 invece di 0.1 + 0.2. Qui non servono eval e Function, prendi semplicemente i numeri dai campi e sommali.

Scrivere la propria calcolatrice o prenderne una già pronta?

Se ti serve una calcolatrice per una pagina, scrivila da te in un'ora seguendo questa guida. Il codice è meno di 100 righe, zero dipendenze, controlli tu il comportamento.

Se stai incorporando il calcolo in WooCommerce o in un modulo d'ordine, non scrivere un parser di espressioni. Prendi i valori dai campi input e calcola direttamente: total = basePrice + optionsSum. È più affidabile, più semplice e più sicuro di qualsiasi calcolo su stringa.

Il codice dell'articolo è una base funzionante. Modifica il layout, aggiungi percentuali, memoria (pulsanti M+/M-/MR), cronologia delle operazioni. Scrivi una calcolatrice una volta con le tue mani, e la domanda "come funziona" sarà chiusa per sempre.