JSFiddle - React, Tailwind, and code Playground

by Mladen Mihajlovic

HTML

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Odometer</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div id="odometer" class="odometer">
        <div class="digit"></div>
        <div class="digit"></div>
        <div class="digit"></div>
    </div>

    <button onclick="increment()">Increment</button>
    <button onclick="decrement()">Decrement</button>

    <script src="script.js"></script>
</body>
</html>

CSS

body {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100vh;
    background-color: #f0f0f0;
    font-family: Arial, sans-serif;
}

#odometer {
    display: flex;
    border: 2px solid #333;
    padding: 10px;
    background-color: #fff;
    border-radius: 5px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.digit {
    font-size: 2rem;
    width: 2rem;
    height: 3rem;
    line-height: 3rem;
    text-align: center;
    overflow: hidden;
    position: relative;
}

.digit div {
    position: absolute;
    width: 100%;
    transition: transform 0.5s ease-in-out;
}

.digit .current {
    transform: translateY(0);
}

.digit .new {
    transform: translateY(100%);
}

button {
    margin: 10px;
    padding: 10px 20px;
    font-size: 1rem;
    border: none;
    background-color: #333;
    color: #fff;
    border-radius: 5px;
    cursor: pointer;
}

button:hover {
    background-color: #555;
}

JavaScript

let currentNumber = 0;

function updateOdometer(number) {
    const digits = document.querySelectorAll('.digit');
    const numberStr = number.toString().padStart(digits.length, '0');
    
    digits.forEach((digit, index) => {
        const currentDigit = digit.querySelector('div.current');
        const newDigitValue = numberStr[index];
        
        if (currentDigit && currentDigit.textContent !== newDigitValue) {
            const newDigit = document.createElement('div');
            newDigit.textContent = newDigitValue;
            newDigit.classList.add('new');
            digit.appendChild(newDigit);
            
            // Trigger reflow for the transition
            newDigit.offsetHeight;
            
            newDigit.style.transform = 'translateY(0)';
            currentDigit.style.transform = 'translateY(-100%)';
            
            setTimeout(() => {
                if (currentDigit && currentDigit.parentNode === digit) {
                    digit.removeChild(currentDigit);
                }
                newDigit.classList.remove('new');
                newDigit.classList.add('current');
            }, 500);
        } else if (!currentDigit) {
            const newDigit = document.createElement('div');
            newDigit.textContent = newDigitValue;
            newDigit.classList.add('current');
            digit.innerHTML = ''; // Clear any initial content
            digit.appendChild(newDigit);
        }
    });
}

function increment() {
    currentNumber++;
    updateOdometer(currentNumber);
}

function decrement() {
    if (currentNumber > 0) {
        currentNumber--;
        updateOdometer(currentNumber);
    }
}

updateOdometer(currentNumber);