class Swiper {
|
constructor() {
|
this.isInitialized = false;
|
this.initSubscribers();
|
this.initHandlers();
|
this.swipe = {
|
startX: null,
|
endX: null,
|
startY: null,
|
endY: null,
|
minSwipe: 50
|
};
|
|
this.pinch = {
|
active: false,
|
startDistance: 0,
|
lastDistance: 0,
|
scale: 1
|
};
|
}
|
|
/*********************************************************************
|
LISTENERS
|
*********************************************************************/
|
initHandlers() {
|
this.touchStartHandler = this.handleTouchStart.bind(this);
|
this.touchMoveHandler = this.handleTouchMove.bind(this);
|
this.touchEndHandler = this.handleTouchEnd.bind(this);
|
}
|
initListeners() {
|
if (this.isInitialized) {
|
return;
|
}
|
this.isInitialized = true;
|
document.addEventListener('touchstart', this.touchStartHandler);
|
document.addEventListener('touchmove', this.touchMoveHandler);
|
document.addEventListener('touchend', this.touchEndHandler);
|
}
|
cleanupListeners() {
|
if (this.subscribers.size > 0) return;
|
this.isInitialized = false;
|
document.removeEventListener('touchstart', this.touchStartHandler);
|
document.removeEventListener('touchmove', this.touchMoveHandler);
|
document.removeEventListener('touchend', this.touchEndHandler);
|
}
|
handleTouchStart(e) {
|
if (e.touches.length === 2) {
|
// Two-finger pinch start
|
const dx = e.touches[0].clientX - e.touches[1].clientX;
|
const dy = e.touches[0].clientY - e.touches[1].clientY;
|
const distance = Math.sqrt(dx*dx + dy*dy);
|
|
this.pinch.active = true;
|
this.pinch.startDistance = this.pinch.lastDistance = distance;
|
this.notify('pinch-start', { distance });
|
|
return; // Don't treat this as a swipe
|
}
|
|
this.swipe.startX = e.touches[0].clientX;
|
this.swipe.startY = e.touches[0].clientY;
|
}
|
handleTouchMove(e) {
|
if (this.pinch.active && e.touches.length === 2) {
|
const dx = e.touches[0].clientX - e.touches[1].clientX;
|
const dy = e.touches[0].clientY - e.touches[1].clientY;
|
const distance = Math.sqrt(dx*dx + dy*dy);
|
|
const scale = distance / this.pinch.startDistance;
|
|
this.pinch.lastDistance = distance;
|
this.pinch.scale = scale;
|
|
this.notify('pinch-move', { e, distance, scale });
|
|
// Direction
|
if (distance > this.pinch.startDistance) {
|
this.notify('pinch-out', { scale });
|
} else {
|
this.notify('pinch-in', { scale });
|
}
|
|
return; // Do not fire swipe logic
|
}
|
this.swipe.endX = e.touches[0].clientX;
|
this.swipe.endY = e.touches[0].clientY;
|
}
|
handleTouchEnd(e) {
|
// Finish pinch
|
if (this.pinch.active) {
|
this.notify('pinch-end', {
|
finalScale: this.pinch.scale
|
});
|
this.pinch.active = false;
|
return;
|
}
|
|
if ((!this.swipe.startX || !this.swipe.endX) ||(!this.swipe.startY || !this.swipe.endY)) return;
|
|
const distanceX = this.swipe.startX - this.swipe.endX;
|
const distanceY = this.swipe.startY - this.swipe.endY;
|
|
if (Math.abs(distanceX) > this.swipe.minSwipe) {
|
let direction = distanceX > 0 ? 'swipe-right' : 'swipe-left';
|
this.notify(direction);
|
}
|
if (Math.abs(distanceY) > this.swipe.minSwipe) {
|
let direction = distanceY > 0 ? 'swipe-up' : 'swipe-down';
|
this.notify(direction);
|
}
|
this.swipe.startX = this.swipe.startY = this.swipe.endX = this.swipe.endY = null;
|
}
|
/*********************************************************************
|
SUBSCRIBERS
|
*********************************************************************/
|
initSubscribers() {
|
this.subscribers = new Set();
|
}
|
|
subscribe(callback) {
|
if (!this.isInitialized) {
|
this.initListeners();
|
}
|
this.subscribers.add(callback);
|
return () => this.subscribers.delete(callback);
|
}
|
unsubscribe(callback) {
|
this.subscribers.delete(callback);
|
if (this.subscribers.size === 0) {
|
this.cleanupListeners();
|
}
|
}
|
|
notify(event, data = {}) {
|
this.subscribers.forEach( callback => {
|
try {
|
callback(event, data);
|
} catch (error) {
|
console.error('Subscriber error:', error);
|
}
|
});
|
}
|
|
/******************************************************************
|
CLEANUP
|
******************************************************************/
|
|
destroy() {
|
this.subscribers.clear();
|
this.cleanupListeners();
|
}
|
}
|
|
window.jvbSwiper = new Swiper();
|