Files
aifo-portal-web/js/home-hero-carousel.js
T
2026-07-02 18:15:06 +08:00

154 lines
4.0 KiB
JavaScript

/**
* Home hero carousel — autoplay + dot navigation + mobile swipe
*/
(function () {
var AUTOPLAY_MS = 5000;
var SWIPE_THRESHOLD = 48;
var MOBILE_MQ = '(max-width: 991.98px)';
function initHomeHeroCarousel(root) {
if (root.getAttribute('data-carousel-ready') === 'true') return;
root.setAttribute('data-carousel-ready', 'true');
var slides = root.querySelectorAll('[data-home-hero-slide]');
var dots = root.querySelectorAll('[data-home-hero-dot]');
var viewport = root.querySelector('.home-hero-carousel__viewport');
if (!slides.length) return;
var index = 0;
var timer = null;
var touchStartX = 0;
var touchStartY = 0;
var touchTracking = false;
function setSlide(nextIndex) {
index = (nextIndex + slides.length) % slides.length;
slides.forEach(function (slide, i) {
var active = i === index;
slide.classList.toggle('is-active', active);
slide.setAttribute('aria-hidden', active ? 'false' : 'true');
});
dots.forEach(function (dot, i) {
var active = i === index;
dot.classList.toggle('is-active', active);
dot.setAttribute('aria-selected', active ? 'true' : 'false');
});
}
function startAutoplay() {
stopAutoplay();
timer = window.setInterval(function () {
setSlide(index + 1);
}, AUTOPLAY_MS);
}
function stopAutoplay() {
if (timer) {
window.clearInterval(timer);
timer = null;
}
}
function isMobileSwipeEnabled() {
return window.matchMedia(MOBILE_MQ).matches;
}
function bindTouchSwipe() {
if (!viewport || viewport.getAttribute('data-swipe-ready') === 'true') return;
viewport.setAttribute('data-swipe-ready', 'true');
viewport.addEventListener(
'touchstart',
function (e) {
if (!isMobileSwipeEnabled() || !e.touches.length) return;
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
touchTracking = true;
stopAutoplay();
},
{ passive: true }
);
viewport.addEventListener(
'touchend',
function (e) {
if (!touchTracking) return;
touchTracking = false;
if (!isMobileSwipeEnabled()) {
startAutoplay();
return;
}
var touch = e.changedTouches[0];
if (!touch) {
startAutoplay();
return;
}
var dx = touch.clientX - touchStartX;
var dy = touch.clientY - touchStartY;
if (Math.abs(dx) < SWIPE_THRESHOLD || Math.abs(dx) <= Math.abs(dy)) {
startAutoplay();
return;
}
if (dx < 0) {
setSlide(index + 1);
} else {
setSlide(index - 1);
}
startAutoplay();
},
{ passive: true }
);
viewport.addEventListener(
'touchcancel',
function () {
touchTracking = false;
startAutoplay();
},
{ passive: true }
);
}
dots.forEach(function (dot) {
dot.addEventListener('click', function () {
var target = Number(dot.getAttribute('data-home-hero-dot'));
if (Number.isNaN(target)) return;
setSlide(target);
startAutoplay();
});
});
root.addEventListener('mouseenter', stopAutoplay);
root.addEventListener('mouseleave', startAutoplay);
root.addEventListener('focusin', stopAutoplay);
root.addEventListener('focusout', function (e) {
if (!root.contains(e.relatedTarget)) startAutoplay();
});
bindTouchSwipe();
setSlide(0);
startAutoplay();
}
function boot() {
document.querySelectorAll('[data-home-hero-carousel]').forEach(initHomeHeroCarousel);
}
window.AifoHomeHeroCarouselInit = initHomeHeroCarousel;
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', boot);
} else {
boot();
}
document.addEventListener('aifo:home-hero-carousel-reload', boot);
})();