Skip to content
Advertisement

Roll Counter Increment by 1

I need help on the script for the counter to increment by one and have a animation of the incremented number, slide to top

enter image description here

something like that on the picture and this is what I have done so far.

        .booking-wrapper .countdown-wrapper .countdown-container .order-list {
        border: 5px solid #959595;
        list-style-type: none;
        padding-left: 0;
        margin-bottom: 0;
        overflow: hidden;
        line-height: 1;
        height: 56px;
        margin-top: 8px;
    }
    
    .booking-wrapper .countdown-wrapper .countdown-container .order-list .order-list-item {
        display: inline;
        font-size: 40px;
        font-weight: bold;
        padding: 0 15px;
    }
 <div class="countdown-container">
             <ul id="order-list" class="order-list">
                 <li class="order-list-item"><span>0</span></li>
                 <li class="order-list-item" style="border-left: 1px solid black;"><span>0</span></li>
                 <li class="order-list-item" style="border-left: 1px solid black; border-right: 1px solid black;"><span>8</span></li>
                 <li class="order-list-item"><span>1</span></li>
             </ul>
       </div>

Advertisement

Answer

You should probably use some existing library for that. But if you want to have your own implementation, then use the following elements:

  • Use CSS transitions, and in particular the transition property and the transitionend event.
  • Build the inner HTML dynamically, so that in the original HTML you only have the container element, which will initially be empty.
  • Let the HTML at the deepest level (for one digit) be 3 lines: on each line you put a digit. For example 8<br>9<br>0. They should form a sequence. Scroll the middle digit into view so that only that digit is visible. Perform the animation upward or downward as needed, and when the animation is complete, update the HTML and reset the scroll offset (which can be the top CSS property).
  • There is no need to have the span elements. Just put the content straight into the li elements.
  • Use promises, which make asynchronous code look better, certainly when using await

So here is how you could do it:

// Utility functions returning promises
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const nextFrame = () => new Promise(resolve => requestAnimationFrame(resolve));
const animate = (elem, prop, value, duration) => {
    return nextFrame().then(() => new Promise(resolve => {
        elem.style.transition = `${prop} ${duration}ms`;
        elem.style[prop] = `${value}px`;
        const done = () => {
            elem.style.transition = `${prop} 0ms`;
            resolve();
        }
        elem.addEventListener("transitionend", done, {once: true});
    })).then(nextFrame);
};

// DOM element wrapper for the counter functionality
class Counter {
    constructor(element, length = 4, upwards = true) {
        this.element = element;
        this._value = 0;
        this.upwards = !!upwards;
        this.digits = Array.from({length}, () => element.appendChild(document.createElement("li")));
    }
    get value() {
        return this._value;
    }
    set value(value) {
        this._value = value;
        const numStr = value.toString().padStart(4, "0").slice(-this.digits.length);
        // Display the current number in the counter element (no animation)
        this.digits.forEach( (digit, i) => {
            // Put three lines, each having a digit, where the middle one is the current one:
            digit.innerHTML = `${(+numStr[i]+(this.upwards ? 9 : 1))%10}<br>${numStr[i]}<br>${(+numStr[i]+(this.upwards ? 1 : 9))%10}`;
            digit.style.top = `${-this.element.clientHeight}px`; // scroll the middle digit into view
        });
    }
    async roll(direction = 1, duration = 500) {
        await nextFrame();
        const numChangingDigits = Math.min(this.digits.length, 
            this.value.toString().length - this.value.toString().search(direction > 0 ? /9*$/ : /0*$/) + 1);
        const numStr = this.value.toString().padStart(4, "0").slice(-numChangingDigits);
        const promises = this.digits.slice(-numChangingDigits).map((digit, i) =>
            animate(digit, "top", (direction > 0) === this.upwards ? -this.element.clientHeight*2 : 0, duration)
        );
        await Promise.all(promises);
        this.value = this.value + direction;
        await nextFrame();
    }
    async rollTo(target, duration = 500, pause = 300) {
        const direction = Math.sign(target - this.value);
        while (this.value !== target) {
            await this.roll(direction, duration);
            await delay(pause);
        }
    }
}

// Demo:
const counter = new Counter(document.getElementById("order-list"), 4, true);
counter.value = 9931;
counter.rollTo(10002, 500, 300);
.order-list {
    border: 5px solid #999;
    list-style-type: none;
    padding-left: 0;
    overflow: hidden;
    height: 50px;
    line-height: 1;
    display: inline-block;
}

.order-list > li {
    display: inline-block;
    font-size: 50px;
    font-weight: bold;
    padding: 0 15px;
    border-left: 1px solid black;
    position: relative;
    top: 0;
}
.order-list > li:first-child {
    border-left: 0;
}
<ul id="order-list" class="order-list"></ul>

There are some arguments you can use to modify the speed of the animations. There is also an argument that determines whether the dials roll upward or downward for getting the next digit.

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement