import type { Ref } from 'vue'; import { onBeforeUnmount, watch, onMounted } from 'vue'; const SMOOTH_PTG = 14 / 15; export default function useMobileTouchMove( inVirtual: Ref, listRef: Ref, callback: (offsetY: number, smoothOffset?: boolean) => boolean, ) { let touched = false; let touchY = 0; let element: HTMLElement | null = null; // Smooth scroll let interval: any = null; const cleanUpEvents = () => { if (element) { element.removeEventListener('touchmove', onTouchMove); element.removeEventListener('touchend', onTouchEnd); } }; const onTouchMove = (e: TouchEvent) => { if (touched) { const currentY = Math.ceil(e.touches[0].pageY); let offsetY = touchY - currentY; touchY = currentY; if (callback(offsetY)) { e.preventDefault(); } // Smooth interval clearInterval(interval); interval = setInterval(() => { offsetY *= SMOOTH_PTG; if (!callback(offsetY, true) || Math.abs(offsetY) <= 0.1) { clearInterval(interval); } }, 16); } }; const onTouchEnd = () => { touched = false; cleanUpEvents(); }; const onTouchStart = (e: TouchEvent) => { cleanUpEvents(); if (e.touches.length === 1 && !touched) { touched = true; touchY = Math.ceil(e.touches[0].pageY); element = e.target as HTMLElement; element!.addEventListener('touchmove', onTouchMove, { passive: false }); element!.addEventListener('touchend', onTouchEnd); } }; const noop = () => {}; onMounted(() => { document.addEventListener('touchmove', noop, { passive: false }); watch( inVirtual, val => { listRef.value.removeEventListener('touchstart', onTouchStart); cleanUpEvents(); clearInterval(interval); if (val) { listRef.value.addEventListener('touchstart', onTouchStart, { passive: false }); } }, { immediate: true }, ); }); onBeforeUnmount(() => { document.removeEventListener('touchmove', noop); }); }