8e9137f725
Memindahkan seluruh file web-beasiswa-untan (assets, data, index.html, SRS/SDD, PROJECT_CONTEXT) ke dalam folder grup ApeNameTeamE. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
54 lines
1.5 KiB
JavaScript
54 lines
1.5 KiB
JavaScript
// =====================================================
|
|
// counter.js — Animasi Counter Statistik
|
|
// Menggunakan IntersectionObserver + requestAnimationFrame
|
|
// =====================================================
|
|
|
|
/**
|
|
* Animasi angka dari 0 ke nilai target
|
|
* @param {HTMLElement} el - Elemen yang menampilkan angka
|
|
* @param {number} target - Angka target
|
|
* @param {number} duration - Durasi animasi dalam ms
|
|
*/
|
|
function animateCounter(el, target, duration = 1800) {
|
|
const startTime = performance.now();
|
|
|
|
function update(currentTime) {
|
|
const elapsed = currentTime - startTime;
|
|
const progress = Math.min(elapsed / duration, 1);
|
|
|
|
// Ease out cubic — percepat di awal, lambat di akhir
|
|
const eased = 1 - Math.pow(1 - progress, 3);
|
|
const current = Math.round(eased * target);
|
|
|
|
el.textContent = current;
|
|
|
|
if (progress < 1) {
|
|
requestAnimationFrame(update);
|
|
}
|
|
}
|
|
|
|
requestAnimationFrame(update);
|
|
}
|
|
|
|
function initCounters() {
|
|
const counters = document.querySelectorAll('[data-counter]');
|
|
if (!counters.length) return;
|
|
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting && !entry.target.dataset.animated) {
|
|
entry.target.dataset.animated = 'true';
|
|
const target = parseInt(entry.target.dataset.counter, 10) || 0;
|
|
animateCounter(entry.target, target);
|
|
}
|
|
});
|
|
},
|
|
{ threshold: 0.6 }
|
|
);
|
|
|
|
counters.forEach(el => observer.observe(el));
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', initCounters);
|