Skip to content

Alt om WordPress, webutvikling — og mer til

🧮 Enkel JavaScript-kalkulator: trinn for trinn-guide

🧮 Enkel JavaScript-kalkulator: trinn for trinn-guide

Trenger du en enkel kalkulator på nettstedet, en fraktkalkulator, boliglånskalkulator eller bare «beregn noe på sparket»? Å skrive en backend for dette er som å skyte spurv med kanon. JavaScript på klientsiden ordner det på en kveld.

Problemet med de fleste guider: de gir deg en jQuery-versjon fra 2017 med eval(), og det er et sikkerhetshull de ikke forteller om. Vi tar en annen vei: bygg en kalkulator i ren JavaScript, med skikkelig layout og uten agendaen «installer tre avhengigheter».

Når du er ferdig med denne artikkelen, har du fungerende kode som adderer, subtraherer, multipliserer og dividerer, og en forståelse av hvorfor eval() bør unngås.

💡 Rask oversikt:

  • HTML: bygg kalkulatoroppmerking med CSS Grid, ikke tabell og center
  • CSS: legg til responsivt grid, knappestil og display
  • JavaScript: skriv klikkfangst, uttrykksinnsamling og beregning uten eval()
  • Sikkerhet: lær hva du bør erstatte eval() med og hvorfor det betyr noe

Trinn 1: HTML, kalkulatoroppmerking

Først bygger vi skjelettet. Ikke noe <center> og <table border="4"> fra 2005, vi bruker CSS Grid: fire kolonner for sifre og operatorer pluss en displayrad øverst.

Opprett en index.html-fil og legg til denne oppmerkingen:

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>

Her er displayet (<input>) skrivebeskyttet: brukeren legger ikke inn data fra tastaturet, men klikker på knapper. Dette beskytter mot søppeltegn i uttrykket. Knappene er inndelt etter data-action-attributter, «number», «operator», «calculate» og «clear», slik at skriptet vet hva det skal gjøre med et klikk. data-value-attributtet lagrer knappeverdien: et siffer eller operasjonstegn.

Merk: displayet har et readonly-attributt, brukeren vil ikke kunne skrive alert('hack') der fra tastaturet.

Trinn 2: CSS, utseende

Nå stilene. Uten CSS ser kalkulatoren ut som en stabel knapper, gridet fikser alt. I den samme index.html, inne i <head>, legg til:

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>

Gridet grid-template-columns: repeat(4, 1fr) gir et jevnt tastatur på fire kolonner, knappene fordeler seg selv. «0»-knappen strekkes over to kolonner via grid-column: span 2, en standardteknikk for kalkulatorlayout.

Fargene her er funksjonelle: oransje for operatorer, blå for «er lik», rød for nullstilling. Brukeren leser umiddelbart knappens formål før hen leser teksten.

Trinn 3: JavaScript, logikk uten eval()

Hoveddelen. La oss koble til skriptet på slutten av <body> i den samme 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>

La oss bryte ned hva som skjer her, linje for linje.

Klikkfangst. addEventListener er festet ikke til hver knapp, men til .buttons-beholderen, dette er hendelsesdelegering. Ved klikk finner e.target.closest('button') den nærmeste knappen (selv om du klikket på tekst inne i den). btn.dataset.action leser data-action-attributtet, og så forgrener logikken seg.

Uttrykksinnsamling. Hvert siffertrykk legger det til i expression-strengen. En operator legges bare til hvis uttrykket ikke er tomt og det siste tegnet ikke er en operator (beskyttelse mot dobbel ++ og */). Displayet oppdateres ved hvert trykk, brukeren ser hele strengen, som på en ekte kalkulator.

Beregning. I stedet for eval(), brukes konstruktøren new Function(). Den utfører også en streng som kode, men med én forskjell: eval() har tilgang til det lokale skopet, mens Function bare har tilgang til det globale skopet. Dette er ingen sølvkule, men for en klientsidekalkulator der brukeren legger inn uttrykket selv, er det et tilstrekkelig isolasjonsnivå. Etter beregning avrundes resultatet til 10 desimaler, ellers blir 0.1 + 0.2 til 0.30000000000000004.

Nullstilling. «C»-knappen nullstiller både expression og displayet.

Idiotsikring. Hvis uttrykket er tomt, ignoreres calculate og operator lydløst. Divisjon med null gir Infinity, isFinite() fanger dette og viser «Error».

Trinn 4: setter alt sammen

La oss kombinere de tre delene til én fil. Her er den komplette index.html som kan åpnes i en nettleser:

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>

Lagre den, åpne i en nettleser, kalkulatoren fungerer umiddelbart, ingen server, ingen npm install. I praksis er denne koden nok for en fraktkalkulator på en landingsside eller et skjema for rabattberegning.

Utseende av kalkulator i ren JavaScript

Hvorfor ikke eval(): sikkerhet som nybegynnerguider ikke nevner

Hvis du har lest gamle veiledninger, har du sikkert sett eval(expression). Ja, det fungerer. Men problemet er at eval() kjører all JavaScript-kode som sendes som en streng. Hvis alert(document.cookie) havner i displayet, vil det bli kjørt. Og hvis displayet er åpent for manuell inntasting, kan du sette inn noe mer alvorlig der.

I vårt tilfelle fungerer displayet i readonly-modus, uttrykket settes sammen kun ved å klikke på knapper. Risikoen er minimal. Men vanen med å bruke eval() er dårlig: en dag kopierer du dette mønsteret inn i et skjema med brukerinndata og får et XSS-hull.

Det finnes tre alternativer, i stigende kompleksitetsgrad:

Tilnærming

Beskyttelse

Kompleksitet

new Function() (som i vår)

Ser ikke lokale variabler, kun globale

Minimal

Manuell parser (tolkning av streng etter operatorer)

Full kontroll over uttrykket

Middels

Web Worker med isolert kontekst

Uttrykket berører ikke DOM i det hele tatt

Høy

For en pedagogisk kalkulator og de fleste reelle scenarioer er new Function() med resultatverifisering nok. Men hvis du bygger inn en kalkulator i et e-handelsskjema (pris × antall), ikke bry deg med strengtolkning i det hele tatt: hent verdier fra felter og beregn direkte: priceInput.value * qtyInput.value. Det trengs ingen eval der i utgangspunktet.

Hvordan utvide: tastaturinndata og operasjonskjeder

Når grunnversjonen fungerer, legg til to funksjoner som gjør et håndverk til et verktøy:

Tastaturinndata. Fest addEventListener('keydown', ...) til document og map taster til knapper: sifre og punktum til data-action="number", Enter til «er lik», Escape til «C», +-*/ til operatorer. Ignorer bokstaver og spesialtegn.

Operasjonskjeder. Akkurat nå beregner kalkulatoren vår «alt på en gang». Men hvis du trykker 5 + 3 = og deretter * 2, vil den ikke fortsette, uttrykket er allerede tilbakestilt til resultatet. Dette fikses enkelt: etter = ikke overskriv expression, men lagre det siste resultatet i en egen lastResult-variabel. Ved neste operator, start uttrykket med lastResult.

Begge forbedringene er 15-20 linjer kode hver. Prøv dem selv som en øvelse: det er den beste måten å forstå at en kalkulator ikke er et «pedagogisk prosjekt», men en normal ingeniørmessig minioppgave.

⁉️🤔 Ofte stilte spørsmål

Kan man klare seg helt uten JavaScript?

For fire regnearter på klientsiden, nei. HTML og CSS er oppmerking og stiler, de gjør ikke beregninger. Beregning på tjenersiden via PHP betyr omlasting av siden ved hvert «=»-trykk. JavaScript er her det eneste praktiske alternativet. Det finnes CSS-triks med calc() og avkrysningsbokser som imiterer logikk, men det er en attraksjon, ikke en kalkulator: maksimalt én operasjon, ingen nullstilling, med forferdelig kode.

Hvordan skiller denne tilnærmingen seg fra «ta en ferdig npm-pakke»?

Ikke en eneste avhengighet. npm install calculator-lib drar med seg 40 pakker til, en bundler og en halv megabyte bundle, for å legge sammen to tall. Vår variant: én HTML-fil, 90 linjer JavaScript, åpnes i enhver nettleser uten server. For de fleste typiske oppgaver, en fraktkalkulator, boliglånsberegning på en landingsside, «beregn kostnad»-skjema, er dette nok.

Hvorfor ikke jQuery?

jQuery for en kalkulator er som en minibuss for å hente brød. I 2017 ga det fortsatt mening: $('.btn') er kortere enn document.querySelectorAll('.btn'), og nettlesere oppførte seg forskjellig. I dag fungerer addEventListener og closest() likt i alle nettlesere, og å dra inn 87 KB komprimert jQuery for fem klikkbehandlere er overdrevent. Hvis jQuery allerede er i prosjektet, bruk det, men for ny kode, skriv i ren JS.

Hva gjør man med flyttall? 0.1 + 0.2 gir fortsatt 0.30000000000000004.

I koden la vi inn Math.round(result * 1e10) / 1e10, dette fjerner halen for ti sifre. For finansielle beregninger (priser, prosenter), bruk heltall, beregn i øre, ikke i kroner: (100 + 200) / 100 = 3.00 i stedet for 0.1 + 0.2. Ingen eval og Function trengs her, bare ta tall fra felter og adder dem.

Bør du skrive din egen kalkulator eller ta en ferdig?

Hvis du trenger en kalkulator til én side, skriv den selv på en time ved hjelp av denne guiden. Koden er under 100 linjer, null avhengigheter, du kontrollerer oppførselen.

Hvis du bygger inn beregning i WooCommerce eller et bestillingsskjema, ikke skriv en uttrykksparser. Hent verdier fra inndatafelter og beregn direkte: total = basePrice + optionsSum. Dette er mer pålitelig, enklere og sikrere enn noen strengberegning.

Koden fra artikkelen er et fungerende fundament. Endre layouten, legg til prosenter, minne (M+/M-/MR-knapper), operasjonshistorikk. Skriv en kalkulator én gang med egne hender, så er spørsmålet «hvordan fungerer dette» lukket for alltid.