| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- // General counter animation system
- function animateCounter(element, options = {}) {
- const defaultOptions = {
- duration: 2000,
- easing: 'easeOutQuart',
- delay: 0,
- separator: true
- };
-
- const config = { ...defaultOptions, ...options };
-
- // Extract number and suffix from element content
- const originalText = element.textContent || element.getAttribute('data-target');
- const target = parseInt(originalText.replace(/[^\d]/g, ''));
- const prefix = originalText.split(/\d/)[0] || '';
- const suffix = originalText.replace(/[\d,]/g, '').replace(prefix, '') || '';
-
- if (isNaN(target)) return;
-
- // Easing functions
- const easingFunctions = {
- linear: t => t,
- easeOutQuart: t => 1 - Math.pow(1 - t, 4),
- easeOutCubic: t => 1 - Math.pow(1 - t, 3),
- easeInOutQuart: t => t < 0.5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t
- };
-
- const easingFunction = easingFunctions[config.easing] || easingFunctions.easeOutQuart;
-
- // Start animation after delay
- setTimeout(() => {
- const startTime = performance.now();
-
- function updateCounter(currentTime) {
- const elapsed = currentTime - startTime;
- const progress = Math.min(elapsed / config.duration, 1);
- const easedProgress = easingFunction(progress);
- const current = Math.floor(target * easedProgress);
-
- // Format number with separator if enabled
- const formattedNumber = config.separator ? current.toLocaleString() : current.toString();
- element.textContent = prefix + formattedNumber + suffix;
-
- if (progress < 1) {
- requestAnimationFrame(updateCounter);
- } else {
- const finalNumber = config.separator ? target.toLocaleString() : target.toString();
- element.textContent = prefix + finalNumber + suffix;
-
- // Dispatch custom event when animation completes
- element.dispatchEvent(new CustomEvent('counterComplete', {
- detail: { target, element }
- }));
- }
- }
-
- // Set initial value
- element.textContent = prefix + '0' + suffix;
- requestAnimationFrame(updateCounter);
- }, config.delay);
- }
- // Auto-detect and animate counters when they become visible
- function createCounterObserver() {
- // Select all elements with counter classes or data attributes
- const counters = document.querySelectorAll(`
- [data-counter],
- [data-count],
- .counter,
- .count,
- .number[data-target],
- .animate-number,
- .count-up
- `);
-
- if (counters.length === 0) return;
-
- const observer = new IntersectionObserver((entries) => {
- entries.forEach(entry => {
- if (entry.isIntersecting) {
- const element = entry.target;
-
- // Get options from data attributes
- const options = {
- duration: parseInt(element.dataset.duration) || 2000,
- easing: element.dataset.easing || 'easeOutQuart',
- delay: parseInt(element.dataset.delay) || 0,
- separator: element.dataset.separator !== 'false'
- };
-
- // Animate the counter
- animateCounter(element, options);
-
- // Stop observing this element
- observer.unobserve(element);
- }
- });
- }, {
- threshold: parseFloat(document.documentElement.dataset.counterThreshold) || 0.3,
- rootMargin: document.documentElement.dataset.counterMargin || '0px 0px -50px 0px'
- });
-
- // Observe all counter elements
- counters.forEach(counter => observer.observe(counter));
- }
- // Manual function to animate specific selectors
- function animateCounters(selector = '[data-counter], .counter, .count', options = {}) {
- const elements = document.querySelectorAll(selector);
-
- elements.forEach((element, index) => {
- const elementOptions = {
- ...options,
- delay: (options.delay || 0) + (options.stagger || 0) * index
- };
-
- animateCounter(element, elementOptions);
- });
- }
- // Initialize when DOM is ready
- function initCounterSystem() {
- // Auto-detect mode (default)
- if (document.documentElement.dataset.counterMode !== 'manual') {
- createCounterObserver();
- }
-
- // Expose global functions
- window.animateCounter = animateCounter;
- window.animateCounters = animateCounters;
- }
- // Multiple initialization methods
- if (document.readyState === 'loading') {
- document.addEventListener('DOMContentLoaded', initCounterSystem);
- } else {
- initCounterSystem();
- }
- // Also expose for manual calling
- window.initCounterSystem = initCounterSystem;
|