Files
wordpress-theme-aksrt/assets/js/mini-touch.js
T
kate522 259dbdbe04 feat(nav): rewrite top header to zibll 1:1 DOM/CSS/JS contract
Replace hybrid site-nav/drawer shell with zibll navbar-top layouts 1/2/3,
body.mobile-navbar-show drawer, wired NavWalker + aksrt_menu_options meta,
nav_folding/chevron, VIP switches, and SPA/PHP schema parity. No dual-class
compat paths.
2026-07-22 11:18:34 +08:00

257 lines
8.1 KiB
JavaScript

/**
* AKSRT Mini Touch — General Direction Gesture Engine
*
* Full direction gesture (top/bottom/left/right) via atan2 angle filtering,
* real-time translate3d follow + cursor, onStart/onIng/onEnd callbacks,
* compatible with swiper/scroll-x containers.
*
* Supports:
* - [data-aksrt-swipe] left/right swipe triggers
* - [data-aksrt-pull-dismiss] pull-to-dismiss with opacity tracking
* - [data-mini-touch="mobile-nav"] mobile menu right-swipe close
* - aksrt.minitouch(el, opts) programmatic API (from aksrt-runtime.js)
*
* @package AKSRT
*/
(function () {
'use strict';
var MIN_SWIPE_DISTANCE = 40;
var MAX_SWIPE_TIME = 300;
function getDirection(angle) {
if (angle >= -45 && angle < 45) return 'right';
if (angle >= 45 && angle < 135) return 'bottom';
if (angle >= -135 && angle < -45) return 'top';
return 'left';
}
// ── Declarative swipe: [data-aksrt-swipe] ──
function bindSwipe(container) {
if (container.dataset.aksrtSwipeBound) return;
container.dataset.aksrtSwipeBound = '1';
var startX = 0, startY = 0, startTime = 0;
var moving = false;
container.addEventListener('touchstart', function (e) {
if (e.touches.length !== 1) return;
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
startTime = Date.now();
moving = true;
}, { passive: true });
container.addEventListener('touchmove', function (e) {
if (!moving) return;
var dx = e.touches[0].clientX - startX;
var dy = e.touches[0].clientY - startY;
if (Math.abs(dy) > Math.abs(dx) * 1.5) moving = false;
}, { passive: true });
container.addEventListener('touchend', function (e) {
if (!moving) return;
moving = false;
var ct = e.changedTouches ? e.changedTouches[0] : e;
var dx = ct.clientX - startX;
var dt = Date.now() - startTime;
if (dt > MAX_SWIPE_TIME || Math.abs(dx) < MIN_SWIPE_DISTANCE) return;
if (Math.abs(ct.clientY - startY) > Math.abs(dx)) return;
var direction = dx > 0 ? 'right' : 'left';
var selector = container.getAttribute('data-aksrt-swipe-' + direction);
if (selector) {
var target = container.querySelector(selector) || document.querySelector(selector);
if (target) { target.click(); return; }
}
container.dispatchEvent(new CustomEvent('aksrt:swipe', {
detail: { direction: direction, distance: Math.abs(dx) },
bubbles: true
}));
}, { passive: true });
}
// ── Pull to dismiss with real-time opacity tracking ──
function bindPullDismiss(el) {
if (el.dataset.aksrtPullBound) return;
el.dataset.aksrtPullBound = '1';
var startY = 0, currentY = 0, pulling = false;
el.addEventListener('touchstart', function (e) {
if (e.touches.length !== 1) return;
// Disable pull when zoomed (scale > 1)
var img = el.querySelector('img');
if (img) {
var transform = getComputedStyle(img).transform;
if (transform && transform !== 'none') {
var parts = transform.match(/matrix\(([^)]+)\)/);
if (parts) {
var vals = parts[1].split(',').map(Number);
var scaleX = Math.sqrt(vals[0] * vals[0] + vals[1] * vals[1]);
if (scaleX > 1.05) return;
}
}
}
if (el.scrollTop > 0) return;
startY = e.touches[0].clientY;
currentY = startY;
pulling = true;
}, { passive: true });
el.addEventListener('touchmove', function (e) {
if (!pulling) return;
currentY = e.touches[0].clientY;
var dy = currentY - startY;
if (dy > 0) {
var opacity = Math.max(0.2, (150 - dy) / 80);
el.style.transform = 'translate3d(0, ' + (dy * 0.5) + 'px, 0)';
el.style.opacity = String(Math.min(1, opacity));
}
}, { passive: true });
el.addEventListener('touchend', function () {
if (!pulling) return;
pulling = false;
var dy = currentY - startY;
if (dy > 120) {
el.style.transform = 'translate3d(0, ' + (dy + 100) + 'px, 0)';
el.style.opacity = '0';
el.style.transition = 'transform 0.2s ease-out, opacity 0.2s ease-out';
setTimeout(function () {
el.dispatchEvent(new CustomEvent('aksrt:pull-dismiss', { bubbles: true }));
el.style.transform = '';
el.style.opacity = '';
el.style.transition = '';
}, 200);
} else {
el.style.transition = 'transform 0.2s ease-out, opacity 0.2s ease-out';
el.style.transform = '';
el.style.opacity = '';
setTimeout(function () { el.style.transition = ''; }, 200);
}
}, { passive: true });
}
// ── [data-mini-touch="..."] declarative gesture ──
function bindMiniTouch(el) {
if (el.dataset.miniTouchBound) return;
el.dataset.miniTouchBound = '1';
var type = el.getAttribute('data-mini-touch');
if (type === 'mobile-nav') {
bindMobileNavSwipe(el);
} else if (type === 'bottom-close') {
bindBottomClose(el);
}
}
function bindMobileNavSwipe(el) {
var startX = 0, currentX = 0, active = false;
el.addEventListener('touchstart', function (e) {
if (e.touches.length !== 1) return;
startX = e.touches[0].clientX;
currentX = startX;
active = true;
}, { passive: true });
el.addEventListener('touchmove', function (e) {
if (!active) return;
currentX = e.touches[0].clientX;
var dx = currentX - startX;
if (dx > 0) {
el.style.transform = 'translateX(' + dx + 'px)';
el.style.opacity = String(Math.max(0.3, 1 - dx / 300));
}
}, { passive: true });
el.addEventListener('touchend', function () {
if (!active) return;
active = false;
var dx = currentX - startX;
if (dx > 80) {
el.style.transform = 'translateX(100%)';
el.style.opacity = '0';
setTimeout(function () {
el.style.transform = '';
el.style.opacity = '';
// Zibll contract: drawer open state lives on body.
document.body.classList.remove('mobile-navbar-show');
}, 200);
} else {
el.style.transition = 'transform 0.2s, opacity 0.2s';
el.style.transform = '';
el.style.opacity = '';
setTimeout(function () { el.style.transition = ''; }, 200);
}
}, { passive: true });
}
function bindBottomClose(el) {
var startY = 0, currentY = 0, active = false;
el.addEventListener('touchstart', function (e) {
if (e.touches.length !== 1) return;
startY = e.touches[0].clientY;
currentY = startY;
active = true;
}, { passive: true });
el.addEventListener('touchmove', function (e) {
if (!active) return;
currentY = e.touches[0].clientY;
var dy = currentY - startY;
if (dy > 0) {
el.style.transform = 'translateY(' + dy + 'px)';
}
}, { passive: true });
el.addEventListener('touchend', function () {
if (!active) return;
active = false;
var dy = currentY - startY;
if (dy > 80) {
el.dispatchEvent(new CustomEvent('aksrt:pull-dismiss', { bubbles: true }));
}
el.style.transform = '';
}, { passive: true });
}
// ── Auto-init ──
function init() {
document.querySelectorAll('[data-aksrt-swipe]').forEach(bindSwipe);
document.querySelectorAll('[data-aksrt-pull-dismiss]').forEach(bindPullDismiss);
document.querySelectorAll('[data-mini-touch]').forEach(bindMiniTouch);
document.addEventListener('aksrt:lightbox-open', function (e) {
var lightbox = e.target;
if (lightbox) bindPullDismiss(lightbox);
});
}
// Register for rehydration
if (window.AksrtFrontend) {
AksrtFrontend.register('aksrt-mini-touch', function (root) {
(root || document).querySelectorAll('[data-aksrt-swipe]').forEach(bindSwipe);
(root || document).querySelectorAll('[data-aksrt-pull-dismiss]').forEach(bindPullDismiss);
(root || document).querySelectorAll('[data-mini-touch]').forEach(bindMiniTouch);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();