Skip to content

Tout pour WordPress, le développement web — et plus encore

🧮 Calculatrice JavaScript simple : guide étape par étape

🧮 Calculatrice JavaScript simple : guide étape par étape

Vous avez besoin d’un simple calculateur sur votre site, d’un calculateur de frais de port, d’un calculateur de crédit ou simplement de «calculer quelque chose à la volée»? Développer un backend pour cela, c’est utiliser un canon pour tirer sur des moineaux. Le JavaScript côté client règle la question en une soirée.

Le problème avec la plupart des tutoriels: ils vous proposent une version jQuery de 2017 avec eval(), ce qui constitue une faille de sécurité qu’ils ne mentionnent pas. Nous allons adopter une approche différente: construire un calculateur en pur JavaScript, avec une mise en page correcte et sans l’obsession d’«installer trois dépendances».

À la fin de cet article, vous disposerez d’un code fonctionnel qui additionne, soustrait, multiplie et divise, et vous comprendrez pourquoi il est préférable d’éviter eval().

💡 Aperçu rapide:

  • HTML: construire le balisage du calculateur avec CSS Grid, sans tableau ni centrage
  • CSS: ajouter une grille responsive, le style des boutons et l’affichage
  • JavaScript: écrire la capture des clics, la collecte de l’expression et le calcul sans eval()
  • Sécurité: apprendre par quoi remplacer eval() et pourquoi c’est important

Étape 1: HTML, le balisage du calculateur

D’abord, nous construisons le squelette. Pas de <center> ni de <table border="4"> de 2005, nous utilisons CSS Grid: quatre colonnes pour les chiffres et les opérateurs, plus une ligne d’affichage en haut.

Créez un fichier index.html et ajoutez ce balisage:

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>

Ici, l’affichage (<input>) est en lecture seule: l’utilisateur ne saisit pas de données au clavier mais clique sur des boutons. Cela protège contre les caractères indésirables dans l’expression. Les boutons sont différenciés par des attributs data-action: «number», «operator», «calculate» et «clear», afin que le script sache quoi faire d’un clic. L’attribut data-value stocke la valeur du bouton: un chiffre ou un signe d’opération.

Remarque: l’affichage possède un attribut readonly, l’utilisateur ne pourra pas y taper alert('hack') depuis le clavier.

Étape 2: CSS, l’apparence

Passons aux styles. Sans CSS, le calculateur ressemble à un empilement de boutons, la grille remet tout en ordre. Dans le même fichier index.html, à l’intérieur de <head>, ajoutez:

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 grille grid-template-columns: repeat(4, 1fr) produit un clavier régulier de quatre colonnes, les boutons se répartissent automatiquement. Le bouton «0» s’étend sur deux colonnes grâce à grid-column: span 2, une technique standard pour la mise en page d’un calculateur.

Les couleurs sont ici fonctionnelles: orange pour les opérateurs, bleu pour «égal», rouge pour la remise à zéro. L’utilisateur perçoit instantanément la fonction du bouton avant même d’en lire le texte.

Étape 3: JavaScript, la logique sans eval()

La partie principale. Connectons le script à la fin de <body> dans le même 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>

Détaillons ce qui se passe ici, ligne par ligne.

Capture des clics. addEventListener n’est pas attaché à chaque bouton mais au conteneur .buttons, c’est la délégation d’événement. Au clic, e.target.closest('button') trouve le bouton le plus proche (même si vous avez cliqué sur le texte à l’intérieur). btn.dataset.action lit l’attribut data-action, puis la logique se ramifie.

Collecte de l’expression. Chaque pression sur un chiffre l’ajoute à la chaîne expression. Un opérateur n’est ajouté que si l’expression n’est pas vide et que le dernier caractère n’est pas un opérateur (protection contre les doubles ++ et */). L’affichage se met à jour à chaque pression, l’utilisateur voit la chaîne complète, comme sur une vraie calculatrice.

Calcul. Au lieu de eval(), le constructeur new Function(). Il exécute également une chaîne comme du code, mais avec une différence: eval() a accès à la portée locale, tandis que Function n’a accès qu’à la portée globale. Ce n’est pas une solution miracle, mais pour un calculateur côté client où l’utilisateur saisit lui-même l’expression, c’est un niveau d’isolation suffisant. Après le calcul, le résultat est arrondi à 10 décimales, sinon 0.1 + 0.2 devient 0.30000000000000004.

Remise à zéro. Le bouton «C» remet à zéro à la fois expression et l’affichage.

Protection anti-erreur. Si l’expression est vide, calculate et operator sont silencieusement ignorés. La division par zéro donne Infinity, isFinite() détecte cela et affiche «Erreur».

Étape 4: tout assembler

Rassemblons les trois parties en un seul fichier. Voici le index.html complet qui peut être ouvert dans un navigateur:

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>

Enregistrez-le, ouvrez-le dans un navigateur, le calculateur fonctionne immédiatement, sans serveur, sans npm install. En pratique, ce code suffit pour un calculateur de frais de port sur une page d’atterrissage ou un formulaire de calcul de remise.

Apparence d'une calculatrice en JavaScript pur

Pourquoi pas eval(): la sécurité que les tutoriels pour débutants ne mentionnent pas

Si vous avez lu d’anciens tutoriels, vous avez probablement vu eval(expression). Oui, cela fonctionne. Mais le problème est que eval() exécute n’importe quel code JavaScript passé sous forme de chaîne. Si alert(document.cookie) arrive dans l’affichage, cela s’exécutera. Et si l’affichage est ouvert à la saisie manuelle, on peut y insérer quelque chose de plus grave.

Dans notre cas, l’affichage fonctionne en mode readonly, l’expression est assemblée uniquement en cliquant sur les boutons. Le risque est minime. Mais l’habitude d’utiliser eval() est mauvaise: un jour, vous copierez ce modèle dans un formulaire avec saisie utilisateur et vous créerez une faille XSS.

Il existe trois alternatives, par ordre croissant de complexité:

Approche

Protection

Complexité

new Function() (comme dans notre cas)

Ne voit pas les variables locales, seulement globales

Minimale

Analyseur manuel (découpage de la chaîne par opérateurs)

Contrôle total sur l’expression

Moyenne

Web Worker avec contexte isolé

L’expression ne touche pas du tout au DOM

Élevée

Pour un calculateur pédagogique et la plupart des scénarios réels, new Function() avec vérification du résultat est suffisant. Mais si vous intégrez un calculateur dans un formulaire e-commerce (prix × quantité), ne vous embêtez pas du tout avec l’analyse de chaîne: récupérez les valeurs des champs et calculez directement: priceInput.value * qtyInput.value. Aucun eval n’est nécessaire ici en premier lieu.

Comment étendre le projet: saisie au clavier et chaînes d’opérations

Quand la version de base fonctionne, ajoutez deux fonctionnalités qui transforment un bricolage en outil:

Saisie au clavier. Attachez addEventListener('keydown', ...) à document et mappez les touches sur les boutons: chiffres et point vers data-action="number", Enter vers «égal», Escape vers «C», +-*/ vers les opérateurs. Ignorez les lettres et les caractères spéciaux.

Chaînes d’opérations. Actuellement, notre calculateur calcule «tout d’un coup». Mais si vous appuyez sur 5 + 3 = puis sur * 2, il ne continuera pas, l’expression est déjà réinitialisée au résultat. Cela se corrige simplement: après =, n’écrasez pas expression, mais enregistrez le dernier résultat dans une variable lastResult distincte. À l’opérateur suivant, commencez l’expression par lastResult.

Ces deux améliorations représentent 15 à 20 lignes de code chacune. Essayez-les vous-même comme exercice: c’est la meilleure façon de comprendre qu’un calculateur n’est pas un «projet pédagogique» mais une mini-tâche d’ingénierie tout à fait normale.

⁉️🤔 Foire aux questions

Peut-on se passer complètement de JavaScript?

Pour quatre opérations arithmétiques côté client, non. HTML et CSS sont du balisage et des styles, ils ne font pas de calculs. Un calcul côté serveur via PHP implique de recharger la page à chaque pression sur «=». JavaScript est ici la seule option pratique. Il existe des astuces CSS avec calc() et des cases à cocher imitant une logique, mais c’est une attraction, pas un calculateur: une seule opération maximum, pas de remise à zéro, avec un code épouvantable.

En quoi cette approche diffère-t-elle de «prendre un package npm tout fait»?

Aucune dépendance. npm install calculator-lib tire 40 packages supplémentaires, un bundler et un demi-mégaoctet de bundle, pour additionner deux nombres. Notre variante: un fichier HTML, 90 lignes de JavaScript, s’ouvre dans n’importe quel navigateur sans serveur. Pour la plupart des tâches courantes, un calculateur de frais de port, un calcul de crédit sur une page d’atterrissage, un formulaire «calculer le coût», cela suffit.

Pourquoi pas jQuery?

jQuery pour un calculateur, c’est comme un minibus pour aller chercher le pain. En 2017, cela avait encore un sens: $('.btn') est plus court que document.querySelectorAll('.btn'), et les navigateurs se comportaient différemment. Aujourd’hui, addEventListener et closest() fonctionnent de la même manière dans tous les navigateurs, et embarquer 87 Ko de jQuery compressé pour cinq gestionnaires de clics est excessif. Si jQuery est déjà sur le projet, utilisez-le, mais pour du nouveau code, écrivez en JS pur.

Que faire avec les nombres à virgule flottante? 0.1 + 0.2 donne toujours 0.30000000000000004.

Dans le code, nous avons mis Math.round(result * 1e10) / 1e10, cela supprime la traîne pour dix chiffres. Pour les calculs financiers (prix, pourcentages), utilisez des entiers, calculez en centimes et non en euros: (100 + 200) / 100 = 3.00 au lieu de 0.1 + 0.2. Aucun eval ni Function n’est nécessaire ici, il suffit de prendre les nombres des champs et de les additionner.

Faut-il écrire votre propre calculateur ou en prendre un tout fait?

Si vous avez besoin d’un calculateur pour une seule page, écrivez-le vous-même en une heure en suivant ce guide. Le code fait moins de 100 lignes, zéro dépendance, vous contrôlez le comportement.

Si vous intégrez un calcul dans WooCommerce ou un formulaire de commande, n’écrivez pas d’analyseur d’expression. Récupérez les valeurs des champs de saisie et calculez directement: total = basePrice + optionsSum. C’est plus fiable, plus simple et plus sûr que n’importe quel calcul sur chaîne.

Le code de l’article est une base fonctionnelle. Modifiez la mise en page, ajoutez des pourcentages, une mémoire (boutons M+/M-/MR), un historique des opérations. Écrivez un calculateur une fois de vos propres mains, et la question «comment ça marche» sera réglée pour toujours.