export default class Counter { private observer: IntersectionObserver; constructor(element: HTMLElement) { /** * Change the text content on the element in a loop * @param textElement * @returns */ function count(textElement: HTMLElement) { const duration = parseInt(textElement.dataset.duration || '2000', 10); // Get the duration from data attribute, or default to 2s const begin = parseInt(textElement.textContent || '0', 10); // Get the start value from the textElement's text content const target = parseInt(textElement.dataset.countTo || '0', 10); // Get the end value from the data attribute const start = performance.now(); if (begin === target) { return; } function draw(now: DOMHighResTimeStamp) { // Counter is done, stop animation if (now >= start + duration) { return (textElement.textContent = Math.trunc(target).toString()); } const progress = (now - start) / duration; const val = easeInOutCubic(progress); const value = begin + (target - begin) * val; textElement.textContent = Math.trunc(value).toString(); requestAnimationFrame(draw); } requestAnimationFrame(draw); } // Gives a ease in and ease out effect function easeInOutCubic(t: number) { return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; } // Create a new intersection observer this.observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { // If the element is in view, animate the number count(entry.target as HTMLElement); // Stop observing the element this.observer.unobserve(entry.target); } }); }); // Start observing the element element .querySelectorAll('.js-counter-text') .forEach((e) => this.observer.observe(e)); } // Destroy method to disconnect the observer destroy() { if (this.observer) { this.observer.disconnect(); this.observer = null; } } }