Files
aperonight/app/javascript/controllers/counter_controller.js
Kevin BATAILLE a8a8c55041 working on header
2025-08-28 14:27:06 +02:00

71 lines
2.0 KiB
JavaScript
Executable File

import { Controller } from "@hotwired/stimulus"
export default class extends Controller {
static values = {
target: { type: Number, default: 0 },
decimal: { type: Boolean, default: false },
duration: { type: Number, default: 2000 }
}
connect() {
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.animate()
this.observer.unobserve(this.element)
}
})
}, { threshold: 0.5 })
this.observer.observe(this.element)
}
disconnect() {
if (this.observer) {
this.observer.disconnect()
}
}
animate() {
// Find the target element with data-target-value
const targetElement = this.element.querySelector('.stat-number');
if (!targetElement) return;
// Get the target value
this.targetValue = parseInt(targetElement.getAttribute('data-target-value'), 10) || this.targetValue;
const startValue = 0;
const startTime = performance.now();
const updateCounter = (currentTime) => {
const elapsedTime = currentTime - startTime;
const progress = Math.min(elapsedTime / this.durationValue, 1);
// Easing function for smooth animation
const easeOutQuart = 1 - Math.pow(1 - progress, 4);
let currentValue = startValue + (this.targetValue - startValue) * easeOutQuart;
if (this.decimalValue && this.targetValue < 10) {
currentValue = currentValue.toFixed(1);
} else {
currentValue = Math.floor(currentValue);
}
// Update only the text content of the target element
targetElement.textContent = currentValue;
if (progress < 1) {
requestAnimationFrame(updateCounter);
} else {
const finalValue = this.decimalValue && this.targetValue < 10
? this.targetValue.toFixed(1)
: this.targetValue;
targetElement.textContent = finalValue;
}
}
requestAnimationFrame(updateCounter);
}
}