I need help on the script for the counter to increment by one and have a animation of the incremented number, slide to top
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 thetransitionend
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 thetop
CSS property). - There is no need to have the
span
elements. Just put the content straight into theli
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.