Skip to content

Allt om WordPress, webbutveckling — och mer därtill

🧮 Enkel javascript-miniräknare: steg för steg-guide

🧮 Enkel javascript-miniräknare: steg för steg-guide

Behöver du en enkel kalkylator på din webbplats, en fraktkalkylator, bolånekalkylator eller bara "räkna ut något direkt"? Att skriva en backend för detta är som att skjuta sparvar med kanon. JavaScript på klientsidan fixar det på en kväll.

Problemet med de flesta guider: de ger dig en jQuery-version från 2017 med eval(), och det är ett säkerhetshål de inte berättar om. Vi tar en annan väg: bygg en kalkylator i ren JavaScript, med vettig layout och utan agendan "installera tre beroenden".

När du har läst klart den här artikeln har du fungerande kod som adderar, subtraherar, multiplicerar och dividerar, och du förstår varför eval() bör undvikas.

💡 Snabb översikt:

  • HTML: bygg kalkylatorns märkspråk med CSS Grid, ingen tabell och centrering
  • CSS: lägg till responsivt grid, knappstyling och display
  • JavaScript: skriv klickfångst, uttrycksinsamling och beräkning utan eval()
  • Säkerhet: lär dig vad du ska ersätta eval() med och varför det spelar roll

Steg 1: HTML, kalkylatorns märkspråk

Först bygger vi skelettet. Ingen <center> och <table border="4"> från 2005, vi använder CSS Grid: fyra kolumner för siffror och operatorer plus en displayrad överst.

Skapa en index.html-fil och lägg till detta märkspråk:

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>

Här är displayen (<input>) skrivskyddad: användaren matar inte in data från tangentbordet utan klickar på knappar. Detta skyddar mot skräptecken i uttrycket. Knapparna är indelade efter data-action-attribut: "number", "operator", "calculate" och "clear", så att skriptet vet vad det ska göra med ett klick. Attributet data-value lagrar knappens värde: en siffra eller ett operationstecken.

Notera: displayen har attributet readonly, användaren kommer inte att kunna skriva alert('hack') där från tangentbordet.

Steg 2: CSS, utseende

Nu stilarna. Utan CSS ser kalkylatorn ut som en stapel knappar, grid fixar allt. I samma index.html, inuti <head>, lägg till:

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>

Griden grid-template-columns: repeat(4, 1fr) ger ett jämnt tangentbord med fyra kolumner, knapparna fördelar sig själva. "0"-knappen sträcks ut över två kolumner med grid-column: span 2, en standardteknik för kalkylatorlayout.

Färgerna här är funktionella: orange för operatorer, blå för "lika med", röd för återställning. Användaren läser omedelbart knappens syfte innan hen läser texten.

Steg 3: JavaScript, logik utan eval()

Huvuddelen. Vi kopplar in skriptet i slutet av <body> i samma 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>

Låt oss bryta ner vad som händer här, rad för rad.

Klickfångst. addEventListener kopplas inte till varje knapp utan till .buttons-containern, detta är händelsedelegation. Vid klick hittar e.target.closest('button') den närmaste knappen (även om du klickade på text inuti den). btn.dataset.action läser attributet data-action, och sedan förgrenas logiken.

Uttrycksinsamling. Varje siffertryck lägger till det i strängen expression. En operator läggs bara till om uttrycket inte är tomt och det sista tecknet inte är en operator (skydd mot dubbel ++ och */). Displayen uppdateras vid varje tryck, användaren ser hela strängen, som på en riktig kalkylator.

Beräkning. Istället för eval() används konstruktorn new Function(). Den kör också en sträng som kod, men med en skillnad: eval() har tillgång till det lokala scopet, medan Function bara har tillgång till det globala scopet. Detta är ingen silverkula, men för en kalkylator på klientsidan där användaren själv matar in uttrycket är det en tillräcklig isoleringsnivå. Efter beräkning avrundas resultatet till 10 decimaler, annars blir 0.1 + 0.2 till 0.30000000000000004.

Återställning. "C"-knappen nollställer både expression och displayen.

Idiotsäkring. Om uttrycket är tomt ignoreras calculate och operator tyst. Division med noll ger Infinity, isFinite() fångar detta och skriver ut "Error".

Steg 4: sätt ihop allt

Låt oss kombinera de tre delarna till en fil. Här är den kompletta index.html som kan öppnas i en webbläsare:

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>

Spara den, öppna i en webbläsare, kalkylatorn fungerar direkt, ingen server, ingen npm install. I praktiken räcker denna kod för en fraktkalkylator på en landningssida eller ett formulär för rabattberäkning.

Utseende av miniräknare i ren JavaScript

Varför inte eval(): säkerhet som nybörjarguider inte nämner

Om du har läst gamla handledningar har du förmodligen sett eval(expression). Ja, det fungerar. Men problemet är att eval() kör vilken JavaScript-kod som helst som skickas som en sträng. Om alert(document.cookie) hamnar i displayen kommer den att köras. Och om displayen är öppen för manuell inmatning kan du stoppa in något allvarligare där.

I vårt fall fungerar displayen i readonly-läge, uttrycket byggs endast genom att klicka på knappar. Risken är minimal. Men vanan att använda eval() är dålig: en dag kopierar du detta mönster till ett formulär med användarinmatning och får ett XSS-hål.

Det finns tre alternativ, i stigande komplexitetsordning:

Metod

Skydd

Komplexitet

new Function() (som i vår)

Ser inte lokala variabler, bara globala

Minimal

Manuell parser (tolka sträng efter operatorer)

Full kontroll över uttrycket

Medel

Web Worker med isolerad kontext

Uttrycket rör inte DOM alls

Hög

För en utbildningskalkylator och de flesta verkliga scenarier räcker new Function() med resultatverifiering. Men om du bäddar in en kalkylator i ett e-handelsformulär (pris × antal), bry dig inte om strängtolkning alls: ta värden från fält och beräkna direkt: priceInput.value * qtyInput.value. Ingen eval behövs där överhuvudtaget.

Hur man bygger ut: tangentbordsinmatning och operationskedjor

När grundversionen fungerar, lägg till två funktioner som förvandlar ett hantverk till ett verktyg:

Tangentbordsinmatning. Koppla addEventListener('keydown', ...) till document och mappa tangenter till knappar: siffror och punkt till data-action="number", Enter till "equals", Escape till "C", +-*/ till operatorer. Ignorera bokstäver och specialtecken.

Operationskedjor. Just nu beräknar vår kalkylator "allt på en gång". Men om du trycker 5 + 3 = och sedan * 2 fortsätter den inte, uttrycket är redan återställt till resultatet. Detta fixas enkelt: efter = skriv inte över expression, utan spara det senaste resultatet i en separat variabel lastResult. Vid nästa operator, starta uttrycket med lastResult.

Båda förbättringarna är 15-20 rader kod vardera. Prova dem själv som en övning: det är det bästa sättet att förstå att en kalkylator inte är ett "utbildningsprojekt" utan en normal ingenjörsmässig miniuppgift.

⁉️🤔 Vanliga frågor

Kan man klara sig helt utan JavaScript?

För fyra räknesätt på klientsidan, nej. HTML och CSS är märkspråk och stilar, de utför inga beräkningar. Beräkning på serversidan via PHP innebär omladdning av sidan vid varje "="-tryck. JavaScript är här det enda praktiska alternativet. Det finns CSS-trick med calc() och kryssrutor som imiterar logik, men det är en attraktion, inte en kalkylator: maximalt en operation, ingen återställning, med fruktansvärd kod.

Hur skiljer sig detta tillvägagångssätt från "ta ett färdigt npm-paket"?

Inte ett enda beroende. npm install calculator-lib drar in 40 paket till, en bundler och en halv megabyte bundle, för att addera två tal. Vår variant: en HTML-fil, 90 rader JavaScript, öppnas i vilken webbläsare som helst utan server. För de flesta typiska uppgifter, en fraktkalkylator, bolåneberäkning på en landningssida, formulär för "beräkna kostnad", räcker detta.

Varför inte jQuery?

jQuery för en kalkylator är som en minibuss för att hämta bröd. 2017 var det fortfarande vettigt: $('.btn') är kortare än document.querySelectorAll('.btn'), och webbläsare betedde sig olika. Idag fungerar addEventListener och closest() likadant i alla webbläsare, och att dra in 87 KB komprimerad jQuery för fem klickhanterare är överdrivet. Om jQuery redan finns i projektet, använd det, men för ny kod, skriv i ren JS.

Vad gör man med flyttal? 0.1 + 0.2 ger fortfarande 0.30000000000000004.

I koden lägger vi in Math.round(result * 1e10) / 1e10, detta tar bort svansen för tio siffror. För finansiella beräkningar (priser, procentsatser), använd heltal, räkna i ören, inte i kronor: (100 + 200) / 100 = 3.00 istället för 0.1 + 0.2. Ingen eval och Function behövs här, ta bara siffror från fält och addera dem.

Ska du skriva din egen kalkylator eller ta en färdig?

Om du behöver en kalkylator för en sida, skriv den själv på en timme med hjälp av denna guide. Koden är mindre än 100 rader, noll beroenden, du kontrollerar beteendet.

Om du bäddar in beräkning i WooCommerce eller ett orderformulär, skriv ingen uttrycksparser. Ta värden från inmatningsfält och beräkna direkt: total = basePrice + optionsSum. Detta är mer tillförlitligt, enklare och säkrare än någon strängberäkning.

Koden från artikeln är en fungerande grund. Ändra layouten, lägg till procentsatser, minne (M+/M-/MR-knappar), operationshistorik. Skriv en kalkylator en gång med egna händer, så är frågan "hur fungerar detta" stängd för alltid.