Files
aifo-portal-web/js/about-us-2-stats.js
2026-07-03 20:34:27 +08:00

97 lines
2.5 KiB
JavaScript

/**
* About Us 2 — achievements stats count-up animation.
*/
(function () {
var SECTION_SELECTOR = "#about-us-2 .au2-stats";
var VALUE_SELECTOR = ".au2-stats__value[data-au2-count]";
var DURATION_MS = 1800;
function easeOutCubic(t) {
return 1 - Math.pow(1 - t, 3);
}
function formatValue(value, useGrouping) {
if (!useGrouping) return String(value);
return value.toLocaleString("en-US");
}
function parseCountOptions(el) {
var raw = el.getAttribute("data-au2-count");
var to = raw ? Number(raw) : NaN;
if (!Number.isFinite(to)) return null;
return {
to: to,
prefix: el.getAttribute("data-au2-prefix") || "",
suffix: el.getAttribute("data-au2-suffix") || "",
grouping: el.getAttribute("data-au2-grouping") === "true",
};
}
function renderValue(el, value, options) {
el.textContent =
options.prefix + formatValue(value, options.grouping) + options.suffix;
}
function animateValue(el, options) {
if (el.dataset.au2Counted === "true") return;
el.dataset.au2Counted = "true";
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
renderValue(el, options.to, options);
return;
}
var start = performance.now();
function tick(now) {
var progress = Math.min((now - start) / DURATION_MS, 1);
var current = Math.round(easeOutCubic(progress) * options.to);
renderValue(el, current, options);
if (progress < 1) {
requestAnimationFrame(tick);
} else {
renderValue(el, options.to, options);
}
}
renderValue(el, 0, options);
requestAnimationFrame(tick);
}
function initSection(section) {
var values = section.querySelectorAll(VALUE_SELECTOR);
if (!values.length) return;
var observer = new IntersectionObserver(
function (entries, obs) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
values.forEach(function (el) {
var options = parseCountOptions(el);
if (options) animateValue(el, options);
});
obs.unobserve(entry.target);
});
},
{ threshold: 0.35, rootMargin: "0px 0px -40px 0px" }
);
observer.observe(section);
}
function init() {
var section = document.querySelector(SECTION_SELECTOR);
if (section) initSection(section);
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();