Files
hesu-JT/js/global-presence.js
T
2026-08-27 11:54:26 +08:00

91 lines
3.2 KiB
JavaScript

/* ============================================================
Global Presence — fullpage screen controller
One screen per wheel gesture / swipe / arrow; dot nav jumps.
Footer (after the last screen) remains freely reachable.
============================================================ */
(function () {
var screens = Array.prototype.slice.call(document.querySelectorAll('.gp-screen'));
var links = Array.prototype.slice.call(document.querySelectorAll('.gp-nav-list a'));
var n = screens.length;
if (!n) return;
var current = 0;
var animating = false;
function topOf(i) { return screens[i].offsetTop; }
function clamp(i) { return Math.max(0, Math.min(n - 1, i)); }
function setActive(i) {
current = i;
links.forEach(function (a) {
a.classList.toggle('active', parseInt(a.getAttribute('data-i'), 10) === i);
});
}
function go(i) {
i = clamp(i);
if (animating || i === current) return;
animating = true;
window.scrollTo({ top: topOf(i), behavior: 'smooth' });
setTimeout(function () { animating = false; }, 650);
}
function syncActive() {
var y = window.scrollY + window.innerHeight * 0.4;
var i = 0;
for (var k = 0; k < n; k++) { if (y >= topOf(k)) i = k; }
if (i !== current) setActive(i);
}
function inFooterZone() { return window.scrollY > topOf(n - 1) + 20; }
// ---- wheel: one screen per gesture; release to footer at the last screen ----
window.addEventListener('wheel', function (e) {
if (animating) { e.preventDefault(); return; }
var d = e.deltaY;
if (inFooterZone()) {
// free scroll inside footer; snap back up when crossing the boundary
if (d < 0 && window.scrollY <= topOf(n - 1) + 80) { e.preventDefault(); go(n - 1); }
return;
}
if (current === n - 1 && d > 0) return; // allow scrolling down into the footer
e.preventDefault();
if (Math.abs(d) < 5) return;
go(current + (d > 0 ? 1 : -1));
}, { passive: false });
// ---- keyboard ----
window.addEventListener('keydown', function (e) {
var k = e.key;
if (k === 'ArrowDown' || k === 'PageDown') { e.preventDefault(); go(current + 1); }
else if (k === 'ArrowUp' || k === 'PageUp') { e.preventDefault(); go(current - 1); }
else if (k === 'Home') { e.preventDefault(); go(0); }
else if (k === 'End') { e.preventDefault(); go(n - 1); }
});
// ---- touch swipe ----
var ty = null;
window.addEventListener('touchstart', function (e) { ty = e.touches[0].clientY; }, { passive: true });
window.addEventListener('touchend', function (e) {
if (ty === null) return;
var dy = ty - e.changedTouches[0].clientY;
if (Math.abs(dy) > 50) go(current + (dy > 0 ? 1 : -1));
ty = null;
}, { passive: true });
// ---- region nav links (overview screen) ----
links.forEach(function (a) {
a.addEventListener('click', function (e) {
e.preventDefault();
var i = parseInt(a.getAttribute('data-i'), 10);
if (!isNaN(i)) go(i);
});
});
// ---- sync + resize ----
window.addEventListener('scroll', syncActive, { passive: true });
window.addEventListener('resize', function () { window.scrollTo({ top: topOf(current), behavior: 'auto' }); });
syncActive();
})();