Skip to content
← 1001 Widgets
N° 001 · ●○○

JS Counter

Interactive counter with +/- and reset buttons. The value changes colour depending on its state.

HTMLCSSJS
What you'll learn
  • Keep a value in a variable
  • Listen for a click with addEventListener
  • Update the text on the page
Why this language

Pourquoi HTML/CSS/JS vanilla et pas React ?

Parce que React, Vue, Svelte : tous ces frameworks font exactement ce que tu vas coder ici, mais en cachant la mécanique. Si tu ne comprends pas le vanilla JS, tu ne comprendras jamais vraiment ce que font les frameworks.

Ce compteur t'apprend le pattern fondamental de tout développement front-end moderne :

ÉTAT → ÉVÉNEMENT → MISE À JOUR DU DOM

C'est littéralement ce qu'est useState() en React. C'est ce qu'est data() en Vue. C'est ce qu'est writable() en Svelte.

Maîtrise ça en vanilla, et les frameworks deviennent évidents.

How it works
  1. L'ÉTAT

    Une seule variable représente la "vérité" du compteur. Règle absolue : on ne lit JAMAIS le DOM pour connaître la valeur actuelle. On lit toujours count. Pourquoi ? Le DOM peut mentir (formatage, innerHTML...). L'état en mémoire, lui, ne ment jamais.

    let count = 0
  2. LA FONCTION D'ACTION

    n peut être positif (+1, +10, +100) ou négatif (-1, -10, -100). Une seule fonction gère tout → moins de code, moins de bugs.

    function add(n) {
    count += n   // on modifie l'état
    update()     // on demande au DOM de se mettre à jour
    }
  3. LE RESET

    Remettre l'état à zéro, puis mettre à jour. Simple mais important : toujours passer par update(), jamais écrire directement dans le DOM depuis ici.

    function reset() {
    count = 0
    update()
    }
  4. LA MISE À JOUR DU DOM

    Cette fonction est le seul endroit où on touche au DOM. C'est le "Single Source of Truth" appliqué. Si demain tu changes le HTML, tu ne modifies qu'ici, pas dans add(), pas dans reset().

    function update() {
    const el = document.getElementById('count')
    
    // On écrit la valeur
    el.textContent = count
    
    // On change la classe CSS selon le signe
    // L'opérateur ternaire enchaîné : si positif → 'positive',
    // sinon si négatif → 'negative', sinon → 'zero'
    el.className = count > 0
    ? 'positive'    // vert
    : count < 0
    ? 'negative'    // rouge
    : 'zero'        // gris
    }
Pitfalls & optimisations

PIÈGE 1 : Lire le DOM comme état ❌ Mauvais : function add(n) { const current = parseInt(el.textContent) el.textContent = current + n } → Tu lis le DOM pour connaître la valeur. Si le texte contient autre chose qu'un chiffre (un espace, une unité...) : bug immédiat.

Fix

Correct : toujours lire count, jamais el.textContent.

PIÈGE 2 : Dupliquer la logique ❌ Mauvais : function increment() { count++; el.textContent = count } function decrement() { count--; el.textContent = count } function add10() { count += 10; el.textContent = count } → Si tu changes l'affichage, tu le changes 3 fois.

Fix

Correct : une seule fonction add(n) + une seule update().

Optimisation 1

Limites min/max function add(n) { // Empêche le compteur de dépasser -100 et +100 count = Math.max(-100, Math.min(100, count + n)) update() }

Optimisation 2

Persistance localStorage function update() { const el = document.getElementById('count') el.textContent = count el.className = count > 0 ? 'positive' : count < 0 ? 'negative' : 'zero' // Sauvegarde automatique à chaque changement localStorage.setItem('compteur', count) }

// Au chargement de la page, on restaure la valeur const saved = localStorage.getItem('compteur') if (saved !== null) { count = parseInt(saved) update() }

Optimisation 3

Animation au changement // Dans ton CSS : // .bump { animation: bump 200ms ease; } // @keyframes bump { 0%,100% { transform: scale(1) } // 50% { transform: scale(1.15) } }

function update() { const el = document.getElementById('count') el.textContent = count el.className = count > 0 ? 'positive' : count < 0 ? 'negative' : 'zero' // Rejoue l'animation à chaque changement el.classList.remove('bump') void el.offsetWidth // force le reflow : astuce pour reset l'animation el.classList.add('bump') }

Optimisation 4

Touches clavier // Pour les power users : flèches haut/bas du clavier document.addEventListener('keydown', (e) => { if (e.key === 'ArrowUp') add(1) if (e.key === 'ArrowDown') add(-1) if (e.key === 'r' || e.key === 'R') reset() })

→ React

ce que tu viens d'apprendre // Ton code vanilla : let count = 0 function add(n) { count += n; update() }

// Devient en React : const [count, setCount] = useState(0) const add = (n) => setCount(prev => prev + n)

// C'est exactement la même chose. // useState = ta variable count // setCount = ta fonction update() automatisée // React re-render = ton el.textContent = count // La logique est identique. Seule la syntaxe change.

Customise

Edit the CSS variables and watch the result in real time.

Variants
Comments
0/1000

Loading comments…

Audience measurement

With your consent, this site measures its traffic with Google Analytics and the Meta pixel, via Google Tag Manager. Nothing is loaded until you choose, and you can change your mind at any time with the "Cookies" link at the bottom of the page.